////////////////////////////////////////////////////////////////////////////
//
//  CryEngine Source File.
//  Copyright (C), Crytek, 2010.
// -------------------------------------------------------------------------
//  File name:   CryGameSpyLobby.cpp
//  Version:     v1.00
//  Created:     29/03/2010 by Paul Mikell.
//  Description: CCryGameSpyLobby member definitions
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////


#include "StdAfx.h"
#include "CryGameSpyLobby.h"
#include "TypeInfo_impl.h"

#if USE_CRYLOBBY_GAMESPY

#if _MSC_VER < 1600
	#ifdef _DEBUG
	#pragma comment( lib, "GameSpy_2.11.00_d.lib" )
	#else
	#pragma comment( lib, "GameSpy_2.11.00.lib" )
	#endif
#else
	#ifdef _DEBUG
	#pragma comment( lib, "GameSpy_2.11.00_VC10_d.lib" )
	#else
	#pragma comment( lib, "GameSpy_2.11.00_VC10.lib" )
	#endif
#endif // _MSVC_VER

#define GAMESPY_INVALID_PEER 0

// GameSpy functions tend to stall if there is no internet connectivity. So functions that get hit can be timed
// and if they take longer than this we can reset and generate an error.
#define GAMESPY_STALL_TIMEOUT	5000

#include "Gamespy2.11.00/Source/common/gsCore.h"
#include "Gamespy2.11.00/Source/gcdkey/gcdkeys.h"
#include "Gamespy2.11.00/Source/gcdkey/gcdkeyc.h"
#include "GameSpy2.11.00/Source/pt/pt.h"

#define GAMESPY_GET_USER_PRIVILEGES_CD_KEY_STATE_PARAM	0
#define GAMESPY_GET_USER_PRIVILEGES_CD_KEY_NOT_STARTED	0
#define GAMESPY_GET_USER_PRIVILEGES_CD_KEY_IN_PROGRESS	1
#define GAMESPY_GET_USER_PRIVILEGES_CD_KEY_SUCCEEDED		2
#define GAMESPY_GET_USER_PRIVILEGES_CD_KEY_FAILED				3
#define GAMESPY_GET_USER_PRIVILEGES_CD_KEY_ERROR_PARAM	1
#define GAMESPY_ONLINE_STATE_PARAM	0
#define GAMESPY_USER_CREDENTIALS_PARAM	0

#define GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM				0

#define GAMESPY_CHECK_PROFANITY_RESULT_NOT_STARTED			0
#define GAMESPY_CHECK_PROFANITY_RESULT_IN_PROGRESS			1
#define GAMESPY_CHECK_PROFANITY_RESULT_FAILED						2
#define GAMESPY_CHECK_PROFANITY_RESULT_PROFANITY				3
#define GAMESPY_CHECK_PROFANITY_RESULT_NO_PROFANITY			4

#define GAMESPY_CHECK_PROFANITY_STRING_MEM_PARAM				0
#define GAMESPY_CHECK_PROFANITY_REQUEST_MEM_PARAM				1
#define GAMESPY_CHECK_PROFANITY_TABLE_ID_MEM_PARAM			2
#define GAMESPY_CHECK_PROFANITY_FIELD_LIST_MEM_PARAM		3
#define GAMESPY_CHECK_PROFANITY_FIELD_DATA_MEM_PARAM		4
#define GAMESPY_CHECK_PROFANITY_SORT_MEM_PARAM					5
#define GAMESPY_CHECK_PROFANITY_FILTER_MEM_PARAM				6
#define GAMESPY_CHECK_PROFANITY_REQUEST_ID_MEM_PARAM		7
#define GAMESPY_CHECK_PROFANITY_STRIPPED_STRING_MEM_PARAM		8

#define GAMESPY_UNIQUENICK_MIN_LENGTH										3
#define GAMESPY_UNIQUENICK_MAX_LENGTH										(GP_UNIQUENICK_LEN - 1)

const char* pGameSpyProfanityTableName = "PROFANITIES";
const char* pGameSpyProfanityFieldName = "profanity";
const char* pGameSpyProfanityFilterFormat = "'%s' like ('%%'+%s+'%%')";
const char* pGameSpyProfanitySort = "recordid";

CCryGameSpyLobbyService::ENATDetectionState CCryGameSpyLobbyService::s_NATDetectionState = eNDS_NotStarted;
ENatType CCryGameSpyLobbyService::s_NATType = eNT_Unknown;

// Description:
//	 Default constructor for CCryGameSpyLobbyService.
CCryGameSpyLobbyService::CCryGameSpyLobbyService(CCryLobby* pLobby, ECryLobbyService service)
:	CCryLobbyService(pLobby, service),
	m_neededGameSpyObjects( 0 ),
	m_pMatchMaking( NULL ),
	m_peer( GAMESPY_INVALID_PEER ),
	m_peerSearching( false ),
	m_groupRoomRefreshState( eTS_NotStarted ),
	m_targetGPStatus( GP_OFFLINE ),
	m_targetGPStatusString( "Offline" ),
	m_targetGPLocationString( "" ),
	m_reportedGPStatus( GP_OFFLINE ),
	m_reportedGPStatusString( "Offline" ),
	m_reportedGPLocationString( "" ),
	m_GPStatusDirty( false ),
	m_havePublicAddress( false ),
	m_reportingState( eRS_Inactive ),
	m_loginCount( 0 ),
	m_requireNewCredentials( false ),
	m_usingCDKey( false ),
	m_initialised( 0 )
{
	m_states[ eGSO_Core ] = eCGSOS_NotInitialized;
	m_states[ eGSO_AvailabilityCheck ] = eCGSOS_NotInitialized;
	m_states[ eGSO_GPConnection ] = eCGSOS_NotInitialized;
	m_states[ eGSO_Peer ] = eCGSOS_NotInitialized;
	m_states[ eGSO_AuthService ] = eCGSOS_NotInitialized;
	m_states[ eGSO_CDKey ] = eCGSOS_NotInitialized;
	m_roomJoinState[ TitleRoom ] = eRJS_NotJoined;
	m_roomJoinState[ GroupRoom ] = eRJS_NotJoined;
	m_roomJoinState[ StagingRoom ] = eRJS_NotJoined;
	m_profileId = 0;

	if (gEnv->IsDedicated())
	{
		m_lobbyServiceFlags&=(~eCLSF_AllowMultipleSessions);
	}

#if USE_CRYLOBBY_GAMESPY_VOIP
	REGISTER_CVAR2( "net_gamespy_voip_enable", &net_gamespy_voip_enable, 1, VF_DUMPTODISK, "Toggles whether GameSpy VOIP enabled or not" );
	REGISTER_CVAR2( "net_gamespy_voip_loopback", &net_gamespy_voip_loopback, 0, VF_CHEAT, "Toggles whether GameSpy microphone capture is played back locally" );
	REGISTER_CVAR2( "net_gamespy_voip_logging", &net_gamespy_voip_logging, 0, VF_CHEAT, "Toggles whether GameSpy VOIP logging is on/off" );
	REGISTER_CVAR2( "net_gamespy_voip_threshold", &net_gamespy_voip_threshold, 0.05f, VF_DUMPTODISK, "Sets the GameSpy VOIP capture volume threshold" );
	REGISTER_CVAR2( "net_gamespy_voip_ptt", &net_gamespy_voip_ptt, 0, VF_DUMPTODISK, "Toggles GameSpy VOIP Push-to-talk mode" );
	REGISTER_CVAR2( "net_gamespy_voip_playback_volume", &net_gamespy_voip_playback_volume, 0.5f, VF_DUMPTODISK, "GameSpy VOIP playback volume" );
#endif
}


// Description:
//	 Destructor for CCryGameSpyLobbyService.
CCryGameSpyLobbyService::~CCryGameSpyLobbyService()
{
}


// Description:
//	 Initialise the CCryGameSpyLobbyService.
// Arguments:
//	 cb - callback
//	 pLobby - lobby
//	 service - service type
// Return:
//	 Error code.
ECryLobbyError CCryGameSpyLobbyService::Initialise( ECryLobbyServiceFeatures features, CryLobbyServiceCallback	pCB )
{
	ECryLobbyError			ret = CCryLobbyService::Initialise(features, pCB);

	SConfigurationParams	info[ 10 ] =
	{
		{ CLCC_GAMESPY_SECRETKEY,		{ NULL } },
		{ CLCC_GAMESPY_NAMESPACEID,	{ NULL } },
		{ CLCC_GAMESPY_TITLE,				{ NULL } },
		{ CLCC_GAMESPY_GAMEVERSION,	{ NULL } },
		{ CLCC_GAMESPY_GAMEVERSIONSTRING, { NULL } },
		{ CLCC_GAMESPY_DISTRIBUTIONID,	{ NULL } },
		{ CLCC_GAMESPY_PARTNERID,		{ NULL } },
		{ CLCC_GAMESPY_PRODUCTID,		{ NULL } },
		{ CLCC_GAMESPY_GAMEID,			{ NULL } },
		{ CLCC_GAMESPY_REQUIREDNICK,{ NULL } }			 
	};

	m_pLobby->GetConfigurationInformation( info, ARRAY_COUNT( info ) );

	m_secretKey = ( char* )info[ 0 ].m_pData;
	m_namespaceID = info[ 1 ].m_32;
	m_title = ( char* )info[ 2 ].m_pData;
	m_gameVersion = info[ 3 ].m_32;
	m_gameVersionString = ( char* )info[ 4 ].m_pData;
	m_distributionID = info[ 5 ].m_32;
	m_partnerID = info[ 6 ].m_32;
	m_productID = info[ 7 ].m_32;
	m_gameID = info[ 8 ].m_32;
	m_requiredNick = ( char* )info[ 9 ].m_pData;

	if ((m_pTCPServiceFactory == NULL) && (features & eCLSO_TCPService))
	{
		m_pTCPServiceFactory = new CCryTCPServiceFactory();

		if ( m_pTCPServiceFactory )
		{
			ret = m_pTCPServiceFactory->Initialise( CCryTCPServiceFactory::k_defaultMaxSockets );
		}
		else
		{
			return eCLE_OutOfMemory;
		}
	}

	if ( (ret == eCLE_Success) && (m_pMatchMaking == NULL) && (features & eCLSO_Matchmaking) )
	{
		m_pMatchMaking = new CCryGameSpyMatchMaking( m_pLobby, this, m_service );

		if ( m_pMatchMaking )
		{
			ret = m_pMatchMaking->Initialise();
		}
		else
		{
			return eCLE_OutOfMemory;
		}
	}
	
#if USE_CRYLOBBY_GAMESPY_VOIP
	if ((ret == eCLE_Success) && (m_pVoice == NULL) && (features & eCLSO_Voice))
	{
		m_pVoice = new CCryGameSpyVoice(m_pLobby, this);

		if (m_pVoice)
		{
			if (m_pVoice->Initialise() != eCLE_Success)
			{
				m_pVoice->Terminate();
				m_pVoice = NULL;
			}
		}
		else
		{
			return eCLE_OutOfMemory;
		}
	}
#endif
	
	if ( (ret == eCLE_Success) && (m_pStats == NULL) && (features & eCLSO_Stats) )
	{
		m_pStats = new CCryGameSpyStats( m_pLobby, this );

		if ( m_pStats )
		{
			ret = m_pStats->Initialise();
		}
		else
		{
			return eCLE_OutOfMemory;
		}
	}

	if ( (ret == eCLE_Success) && (m_pFriends == NULL) && (features & eCLSO_Friends) )
	{
		m_pFriends = new CCryGameSpyFriends( m_pLobby, this );

		if ( m_pFriends )
		{
			ret = m_pFriends->Initialise();
		}
		else
		{
			return eCLE_OutOfMemory;
		}
	}

	if ( (ret == eCLE_Success) && (m_pFriendsManagement == NULL) && (features & eCLSO_Friends) )
	{
		m_pFriendsManagement = new CCryGameSpyFriendsManagement( m_pLobby, this );

		if ( m_pFriendsManagement )
		{
			ret = m_pFriendsManagement->Initialise();
		}
		else
		{
			return eCLE_OutOfMemory;
		}
	}

	if ((ret == eCLE_Success) && (m_pLobbyUI == NULL) && (features & eCLSO_LobbyUI))
	{
		m_pLobbyUI = new CCryGameSpyLobbyUI(m_pLobby, this);

		if (m_pLobbyUI)
		{
			ret = m_pLobbyUI->Initialise();
		}
		else
		{
			ret = eCLE_OutOfMemory;
		}
	}

	if (( ret == eCLE_Success ) && (m_pDLCStore == NULL) && (features & eCLSO_Base))
	{
		m_pDLCStore = new CCryGameSpyDLCStore( m_pLobby, this );

		if ( m_pDLCStore )
		{
			ret = m_pDLCStore->Initialise();
		}
		else
		{
			ret = eCLE_OutOfMemory;
		}
	}
	
	if (( ret == eCLE_Success ) && (features & eCLSO_Base))
	{
		m_pLobby->SetServicePacketEnd(m_service, eGameSpyPT_EndType);
		CryInterlockedIncrement( &m_initialised );
	}

	if (pCB)
	{
		pCB( ret, m_pLobby, m_service );
	}

	return ret;
}


// Description:
//	 Terminate the CCryGameSpyLobbyService.
// Arguments:
//	 cb - callback
//	 pLobby - lobby
//	 service - service type
// Return:
//	 Error code.
ECryLobbyError CCryGameSpyLobbyService::Terminate( ECryLobbyServiceFeatures features, CryLobbyServiceCallback pCB )
{
	ECryLobbyError			ret = eCLE_NotInitialised;

	if ( m_pTCPServiceFactory || m_pMatchMaking ||
#if USE_CRYLOBBY_GAMESPY_VOIP
		m_pVoice ||
#endif
		m_pStats || m_pFriends || m_pFriendsManagement || m_pLobbyUI)
	{
		ret = CCryLobbyService::Terminate(features, pCB);

		if ( m_pTCPServiceFactory && (features & eCLSO_TCPService) )
		{
			ECryLobbyError error = m_pTCPServiceFactory->Terminate( false );
			m_pTCPServiceFactory = NULL;

			if ( ret == eCLE_Success )
			{
				ret = error;
			}
		}

		if ( m_pMatchMaking && (features & eCLSO_Matchmaking) )
		{
			ECryLobbyError error = m_pMatchMaking->Terminate();
			m_pMatchMaking = NULL;

			if ( ret == eCLE_Success )
			{
				ret = error;
			}
		}

#if USE_CRYLOBBY_GAMESPY_VOIP
		if (m_pVoice && (features & eCLSO_Voice))
		{
			ECryLobbyError error = m_pVoice->Terminate();
			m_pVoice = NULL;

			if (ret == eCLE_Success)
			{
				ret = error;
			}
		}
#endif

		if ( m_pStats && (features & eCLSO_Stats) )
		{
			ECryLobbyError error = m_pStats->Terminate();
			m_pStats = NULL;

			if ( ret == eCLE_Success )
			{
				ret = error;
			}
		}

		// N.B. terminate friends management *before* friends as it holds a reference to friends and
		// we don't want it trying to use any friends functionality if friends has been terminated
		if (m_pFriendsManagement && (features & eCLSO_Friends))
		{
			ECryLobbyError error = m_pFriendsManagement->Terminate();
			m_pFriendsManagement = NULL;

			if (ret == eCLE_Success)
			{
				ret = error;
			}
		}

		if (m_pFriends && (features & eCLSO_Friends))
		{
			ECryLobbyError error = m_pFriends->Terminate();
			m_pFriends = NULL;

			if (ret == eCLE_Success)
			{
				ret = error;
			}
		}

		if (m_pLobbyUI && (features & eCLSO_LobbyUI))
		{
			ECryLobbyError error = m_pLobbyUI->Terminate();
			m_pLobbyUI = NULL;

			if (ret == eCLE_Success)
			{
				ret = error;
			}
		}

		if ( m_pDLCStore && (features & eCLSO_Base))
		{
			ECryLobbyError error = m_pDLCStore->Terminate();
			m_pDLCStore = NULL;

			if ( ret == eCLE_Success )
			{
				ret = error;
			}
		}
	}

	if (features & eCLSO_Base)
	{
		PeerShutdown();

		if ( m_states[ eGSO_GPConnection ] != eCGSOS_NotInitialized )
		{
			gpDestroy( &m_GPConnection );
			SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_NotInitialized, eCLE_Success );
		}

		if ( m_states[ eGSO_Core ] != eCGSOS_NotInitialized )
		{
			gsCoreShutdown();			// Required by SAKE and potentially others
			SetGameSpyObjectState( eGSO_Core, eCGSOS_NotInitialized, eCLE_Success );
		}

		if ( ret == eCLE_Success )
		{
			CryInterlockedDecrement( &m_initialised );
			if (m_initialised < 0)
			{
				m_initialised = 0;
			}
		}
	}

	if ( pCB )
	{
		pCB( ret, m_pLobby, m_service );
	}

	return ret;
}


// Description:
//	 Tick the CCryGameSpyLobbyService.
// Arguments:
//	 tv - time
void CCryGameSpyLobbyService::Tick( CTimeValue tv )
{
	if ( !m_initialised )
	{
		return;
	}

	if ( m_neededGameSpyObjects & eGSOB_Peer )
	{
		m_neededGameSpyObjects |= eGSOB_GPConnection;
	}

	if ( m_neededGameSpyObjects & eGSOB_AuthService )
	{
		m_neededGameSpyObjects |= eGSOB_GPConnection;
	}

	if ( m_neededGameSpyObjects & eGSOB_GPConnection )
	{
		m_neededGameSpyObjects |= eGSOB_AvailabilityCheck;
	}

	if ( m_neededGameSpyObjects & eGSOB_CDKey )
	{
		m_neededGameSpyObjects |= eGSOB_AvailabilityCheck;
	}

	if ( m_neededGameSpyObjects & eGSOB_AvailabilityCheck )
	{
		m_neededGameSpyObjects |= eGSOB_Core;
	}

	if ( m_pLobby->MutexTryLock() )
	{
		if ( ( m_neededGameSpyObjects & eGSOB_Core ) || ( m_states[ eGSO_Core ] != eCGSOS_NotInitialized ) )
		{
			switch ( m_states[ eGSO_Core ] )
			{
			case eCGSOS_NotInitialized:
				gsCoreInitialize();
				SetGameSpyObjectState( eGSO_Core, eCGSOS_Initialized, eCLE_Success );
				break;
			}

			gsCoreThink((gsi_time)0);							// Expects to be told how long it can take, 0 means just do one pass.
		}

		if ( ( ( m_neededGameSpyObjects & eGSOB_AvailabilityCheck ) || ( m_states[ eGSO_AvailabilityCheck ] != eCGSOS_NotInitialized ) ) && ( m_states[ eGSO_Core ] == eCGSOS_Initialized ) )
		{
			switch ( m_states[ eGSO_AvailabilityCheck ] )
			{
			case eCGSOS_NotInitialized:
				StartGameSpyAvailabilityCheck();
				break;
			case eCGSOS_Initializing:
				WaitForGameSpyAvailabilityCheck();
				break;
			}
		}

		if ( ( m_neededGameSpyObjects & eGSOB_AuthService ) && ( m_states[ eGSO_GPConnection ] == eCGSOS_Connected ) )
		{
			switch ( m_states[ eGSO_AuthService ] )
			{
			case eCGSOS_NotInitialized:
				AuthServiceStartLogin();
				break;
			}
		}

		if ( ( ( m_neededGameSpyObjects & eGSOB_GPConnection ) || ( m_states[ eGSO_GPConnection ] != eCGSOS_NotInitialized ) ) && ( m_states[ eGSO_AvailabilityCheck ] == eCGSOS_Initialized ) )
		{
			if ( m_states[ eGSO_GPConnection ] != eCGSOS_NotInitialized )
			{
				gpProcess( &m_GPConnection );
			}

			switch ( m_states[ eGSO_GPConnection ] )
			{
			case eCGSOS_NotInitialized:
				InitializeGPConnection();
				break;
			case eCGSOS_Initialized:
				if ( m_neededGameSpyObjects & eGSOB_GPConnection )
				{
					GameSpyConnect();
				}
				break;
			case eCGSOS_Connected:
				if ( m_GPStatusDirty )
				{
					if ( gpSetStatus( &m_GPConnection, m_targetGPStatus, m_targetGPStatusString.c_str(), m_targetGPLocationString.c_str() ) == GP_NO_ERROR )
					{
						m_reportedGPStatus = m_targetGPStatus;
						m_reportedGPStatusString = m_targetGPStatusString;
						m_reportedGPLocationString = m_targetGPLocationString;
						m_GPStatusDirty = false;
					}
				}
				break;
			}
		}

		if ( ( ( m_neededGameSpyObjects & eGSOB_Peer ) || ( m_states[ eGSO_Peer ] != eCGSOS_NotInitialized ) ) && ( m_states[ eGSO_AvailabilityCheck ] == eCGSOS_Initialized ) )
		{
			if ( m_states[ eGSO_Peer ] != eCGSOS_NotInitialized )
			{
				if (m_peer != GAMESPY_INVALID_PEER)
				{
					peerThink( m_peer );
				}
				else
				{
					NetLog("[GameSpy]: peerThink() has invalid peer");
				}
			}

			switch ( m_states[ eGSO_Peer ] )
			{
			case eCGSOS_NotInitialized:
				InitializePeer();
				break;
			case eCGSOS_Initialized:
				PeerConnect();
				break;
			case eCGSOS_Connected:
				if ( !m_havePublicAddress )
				{
					if ( m_reportingState == eRS_Inactive )
					{
						// Briefly report as a server until GameSpy tells us our public IP. This
						// won't cause the peer to be reported in session searches because we'll
						// never report a valid session ID until we create a real staging room.

						IDatagramSocketPtr	pSocket = m_pLobby->GetInternalSocket(m_service);

						if (pSocket)
						{
							SOCKET							sysSocket = pSocket->GetSysSocket();
							sockaddr						addr;
							int									addrLen = sizeof ( addr );
							uint16							port;

							getsockname( sysSocket, &addr, &addrLen );
							port = ntohs( ( ( sockaddr_in* )&addr )->sin_port );
							peerStartReportingWithSocket( m_peer, sysSocket, port );
							m_reportingState = eRS_Server;
						}
					}
				}
				else
				{
					if ( m_reportingState == eRS_Server )
					{
						peerStopGame( m_peer );
						m_reportingState = eRS_Inactive;
					}
				}

				TickRoomOps();
				break;
			}
		}

		if ( ( ( m_neededGameSpyObjects & eGSOB_CDKey ) || ( m_states[ eGSO_CDKey ] != eCGSOS_NotInitialized ) ) && ( m_states[ eGSO_AvailabilityCheck ] == eCGSOS_Initialized ) )
		{
			if ( m_states[ eGSO_CDKey ] != eCGSOS_NotInitialized )
			{
				gcd_think();
			}

			switch ( m_states[ eGSO_CDKey ] )
			{
			case eCGSOS_NotInitialized:
				SetGameSpyObjectState( eGSO_CDKey, eCGSOS_Initializing, eCLE_Success );		
				if ( gcd_init( m_gameID, m_title ) == 0 )
				{
					SetGameSpyObjectState( eGSO_CDKey, eCGSOS_Initialized, eCLE_Success );
				}
				else
				{
					SetGameSpyObjectState( eGSO_CDKey, eCGSOS_NotInitialized, eCLE_InternalError );
				}
				break;
			}
		}

		switch ( s_NATDetectionState )
		{
		case eNDS_NotStarted:
			// FALL THROUGH
		case eNDS_Failed:
			if ( NNStartNatDetection( NATDetectionCallback ) == ne_noerror )
			{
				s_NATDetectionState = eNDS_InProgress;
			}
			else
			{
				s_NATDetectionState = eNDS_Failed;
			}
			break;
		case eNDS_Succeeded:
			s_NATDetectionState = eNDS_Dispatched;
			TO_GAME_FROM_LOBBY( &CCryGameSpyLobbyService::DispatchNATType, this );
			break;
		}

		NNThink();

		for (uint32 i = 0; i < MAX_LOBBY_TASKS; i++)
		{
			STask* pTask = GetTask(i);

			if (pTask->used && pTask->running)
			{
				if (pTask->error == eCLE_Success)
				{
					switch (pTask->startedTask)
					{
					case eT_GetUserPrivileges:
						TickGetUserPrivileges(i);
						break;
					case eT_UserSignOut:
						TickUserSignOut(i);
						break;
					case eT_GetUserCredentials:
						TickGetUserCredentials( i );
						break;
					case eT_CheckProfanity:
						TickCheckProfanity( i );
						break;
					}
				}
			}
		}

		m_workQueue.Flush( false );

		if ( m_pMatchMaking )
		{
			m_pMatchMaking->Tick( tv );
		}

#if USE_CRYLOBBY_GAMESPY_VOIP
		if (net_gamespy_voip_enable && m_pVoice && gEnv->bMultiplayer)
		{
			m_pVoice->Tick(tv);
		}
#endif

		if ( m_pStats )
		{
			m_pStats->Tick( tv );
		}

		if (m_pFriends != NULL)
		{
			m_pFriends->Tick(tv);
		}

		if (m_pFriendsManagement != NULL)
		{
			m_pFriendsManagement->Tick(tv);
		}

		if ( m_pDLCStore != NULL )
		{
			m_pDLCStore->Tick( tv );
		}

		m_pLobby->MutexUnlock();
	}

	if ( m_pTCPServiceFactory )
	{
		m_pTCPServiceFactory->Tick( tv );
	}
}


// Description:
//	 Get matchmaking.
// Return:
//   Pointer to matchmaking.
ICryMatchMaking* CCryGameSpyLobbyService::GetMatchMaking()
{
	return m_pMatchMaking;
}


// Description:
//	 Get voice.
// Return:
//   Pointer to voice.
ICryVoice* CCryGameSpyLobbyService::GetVoice()
{
#if USE_CRYLOBBY_GAMESPY_VOIP
	return m_pVoice;
#else
	return NULL;
#endif
}


// Description:
//	 Get stats.
// Return:
//   Pointer to stats.
ICryStats* CCryGameSpyLobbyService::GetStats()
{
	return m_pStats;
}


// Description:
//	 Get lobby UI.
// Return:
//   Pointer to lobby UI.
ICryLobbyUI* CCryGameSpyLobbyService::GetLobbyUI()
{
	return m_pLobbyUI;
}


// Description:
//	 Get friends.
// Return:
//   Pointer to friends.
ICryFriends* CCryGameSpyLobbyService::GetFriends()
{
	return m_pFriends;
}


// Description:
//	 Get friends management.
// Return:
//   Pointer to friends management.
ICryFriendsManagement* CCryGameSpyLobbyService::GetFriendsManagement()
{
	return m_pFriendsManagement;
}


ICryReward* CCryGameSpyLobbyService::GetReward()
{
	return NULL;
}


ICryDLCStore* CCryGameSpyLobbyService::GetDLCStore()
{
	return m_pDLCStore;
}


void CCryGameSpyLobbyService::SetNeedGPConnection( bool needGPConnection )
{
	SetNeedGameSpyObject( eGSOB_GPConnection, needGPConnection );
}


void CCryGameSpyLobbyService::SetNeedPeer( bool needPeer )
{
	SetNeedGameSpyObject( eGSOB_Peer, needPeer );
}

void CCryGameSpyLobbyService::SetNeedCDKey( bool needCDKey )
{
	SetNeedGameSpyObject( eGSOB_CDKey, needCDKey );
}

void CCryGameSpyLobbyService::SetNeedGameSpyObject( EGameSpyObjectBit object, bool state )
{
	if ( state )
	{
		m_neededGameSpyObjects |= object;
	}
	else
	{
		m_neededGameSpyObjects &= ~object;
	}
}


bool CCryGameSpyLobbyService::HaveGPConnection()
{
	return m_states[ eGSO_GPConnection ] == eCGSOS_Connected;
}

const GPConnection* const CCryGameSpyLobbyService::GetGPConnection()
{
	return &m_GPConnection;
}


bool CCryGameSpyLobbyService::HavePeer()
{
	return m_states[ eGSO_Peer ] == eCGSOS_Connected;
}


// Description:
//	 Get the user id of a user signed in to this service locally.
// Arguments:
//	 user - The pad number of the local user.
// Return:
//   The CryUserID of the user.
CryUserID CCryGameSpyLobbyService::GetUserID( uint32 user )
{
	CryUserID				result = CryUserInvalidID;

	CRY_ASSERT_MESSAGE( user == 0, "CCryGameSpyLobbyService::GetUserID: user != 0 not supported" );

	if ( m_states[ eGSO_GPConnection ] == eCGSOS_Connected )
	{
		SCryGameSpyUserID* pResultRaw = new SCryGameSpyUserID(m_profileId);

		if ( pResultRaw != NULL )
		{
			result = pResultRaw;
		}
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::GetUserID called with GPConnection not in connected state" );
	}

	return result;
}


// Description:
//	 Called when a packet is received.
// Arguments:
//	 addr - address
//	 pData - data
//	 length - length
void CCryGameSpyLobbyService::OnPacket( const TNetAddress& addr, CCryLobbyPacket* pPacket )
{
	const uint8* pData = pPacket->GetReadBuffer();
	uint32 length = pPacket->GetReadBufferSize();

	if ( ( length >= 2 ) && ( pData[ 0 ] == QR_MAGIC_1 ) && ( pData[ 1 ] == QR_MAGIC_2 ) )
	{
		if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
		{
			// GameSpy query must be NUL-terminated and peerParseQuery won't do it for us.
			char* pQuery = new char[ length + 1 ];

			if ( pQuery )
			{
				sockaddr saddr;
				int saddrLen = sizeof ( saddr );

				memcpy( pQuery, pData, length );
				pQuery[ length ] = '\0';
				ConvertAddr( addr, &saddr, &saddrLen );
				peerParseQuery( m_peer, reinterpret_cast< char* >( const_cast< uint8* >( pData ) ), length, &saddr );
				delete[] pQuery;
			}
		}
	}
	else if ((length >= 6) && (pData[0] == NN_MAGIC_0) && (pData[1] == NN_MAGIC_1) && (pData[2] == NN_MAGIC_2) && (pData[3] == NN_MAGIC_3) && (pData[4] == NN_MAGIC_4) && (pData[5] == NN_MAGIC_5))
	{
		// NAT Negotiation packet
		sockaddr_in saddr;

		if (ConvertAddr(addr, &saddr))
		{
			NNProcessData((char*)pData, length, &saddr);
		}
	}
	else
	{
		if ( m_pMatchMaking )
		{
			m_pMatchMaking->OnPacket( addr, pPacket );
		}

#if USE_CRYLOBBY_GAMESPY_VOIP
		if (m_pVoice)
		{
			m_pVoice->OnPacket(addr, pPacket);
		}
#endif
	}
}


// Description:
//	 Empty implementation of OnError for ICryLobbyService that doesn't share
//	 socket with ICryLobby.
// Arguments:
//	 addr - address
//	 error - error
//	 sendID - send ID
void CCryGameSpyLobbyService::OnError( const TNetAddress& addr, ESocketError error, CryLobbySendID sendID )
{
	if ( m_pMatchMaking )
	{
		m_pMatchMaking->OnError( addr, error, sendID);
	}
}


// Description:
//	 Called on error.
// Arguments:
//	 addr - address
//	 error - error
void CCryGameSpyLobbyService::OnError( const TNetAddress& addr, ESocketError error )
{
}


// Description:
//	 Empty implementation of OnSendComplete for ICryLobbyService that doesn't
//	 share socket with ICryLobby.
// Arguments:
//	 addr - address
//	 sendID - send ID
void CCryGameSpyLobbyService::OnSendComplete( const TNetAddress& addr, CryLobbySendID sendID )
{
	if ( m_pMatchMaking )
	{
		m_pMatchMaking->OnSendComplete( addr, sendID );
	}
}

// Description:
//	 Get local user name.
// Return:
//	 Local user name.
const char* CCryGameSpyLobbyService::GetLocalUserName()
{
	return m_uniqueNick;
}


// Description:
//	 Refresh the group rooms.
// Return:
//	 Error code.
ECryLobbyError CCryGameSpyLobbyService::PeerRefreshGroupRooms()
{
	ECryLobbyError			error = eCLE_Success;

	CRY_ASSERT_MESSAGE( m_states[ eGSO_Peer ] == eCGSOS_Connected, "CCryGameSpyLobbyService::PeerRefreshGroupRooms requires peer to be in connected state" );

	if ( m_states[ eGSO_Peer ] != eCGSOS_Connected )
	{
		return eCLE_ObjectNotConnected;
	}

	switch ( m_groupRoomRefreshState )
	{
	case eTS_NotStarted:

		m_groupRoomRefreshState = eTS_InProgress;
		m_groupRooms.clear();
		peerListGroupRooms( m_peer, 0, &PeerListGroupRoomsCallback, this, PEERFalse );
		break;

	case eTS_InProgress:

		error = eCLE_JoinInProgress;
		break;

	case eTS_Succeeded:
	case eTS_Failed:

		m_groupRoomRefreshState = eTS_InProgress;
		m_groupRooms.clear();
		peerListGroupRooms( m_peer, 0, &PeerListGroupRoomsCallback, this, PEERFalse );
		break;
	}
	
	return error;
}


// Description:
//	 Join the title room.
void CCryGameSpyLobbyService::PeerJoinTitleRoom( peerJoinRoomCallback cb, void* pParam )
{
	SRoomOp*				pNewRoomOp = new SRoomOp();

	if ( pNewRoomOp )
	{
		pNewRoomOp->m_opType = eROT_Join;
		pNewRoomOp->m_roomType = TitleRoom;
		pNewRoomOp->m_state = eTS_NotStarted;
		pNewRoomOp->m_cb = cb;
		pNewRoomOp->m_pParam = pParam;
		m_roomOpQueue.push( pNewRoomOp );
	}
	else
	{
		if ( cb )
		{
			cb( m_peer, PEERFalse, PEERJoinFailed, TitleRoom, pParam );
		}
	}
}


// Description:
//	 Leave the title room.
void CCryGameSpyLobbyService::PeerLeaveTitleRoom()
{
	SRoomOp*								pNewRoomOp = new SRoomOp();

	if ( pNewRoomOp )
	{
		pNewRoomOp->m_opType = eROT_Leave;
		pNewRoomOp->m_roomType = TitleRoom;
		pNewRoomOp->m_state = eTS_NotStarted;
		m_roomOpQueue.push( pNewRoomOp );
	}
	else
	{
		NetLogAlways( "[GameSpy] CCryGameSpyLobbyService::PeerLeaveTitleRoom: out of memory");
	}
}


// Description:
//	 Join a group room.
void CCryGameSpyLobbyService::PeerJoinGroupRoom( int groupID, peerJoinRoomCallback cb, void* pParam  )
{
	SJoinGroupRoomOp*				pNewRoomOp = new SJoinGroupRoomOp();

	if ( pNewRoomOp )
	{
		pNewRoomOp->m_opType = eROT_Join;
		pNewRoomOp->m_roomType = GroupRoom;
		pNewRoomOp->m_state = eTS_NotStarted;
		pNewRoomOp->m_cb = cb;
		pNewRoomOp->m_pParam = pParam;
		pNewRoomOp->m_groupID = groupID;
		m_roomOpQueue.push( pNewRoomOp );
	}
	else
	{
		if ( cb )
		{
			cb( m_peer, PEERFalse, PEERJoinFailed, GroupRoom, pParam );
		}
	}
}


// Description:
//	 Leave the current group room.
void CCryGameSpyLobbyService::PeerLeaveGroupRoom()
{
	SRoomOp*								pNewRoomOp = new SRoomOp();

	if ( pNewRoomOp )
	{
		pNewRoomOp->m_opType = eROT_Leave;
		pNewRoomOp->m_roomType = GroupRoom;
		pNewRoomOp->m_state = eTS_NotStarted;
		m_roomOpQueue.push( pNewRoomOp );
	}
	else
	{
		NetLogAlways( "[GameSpy] CCryGameSpyLobbyService::PeerLeaveGroupRoom: out of memory");
	}
}


// Description:
//	 Create a staging room.
// Arguments:
//	 pName - name of room
//	 maxPlayers - maximum number of players
//	 pPassword - password
//	 cb - callback
//	 pParam - callback parameter
void CCryGameSpyLobbyService::PeerCreateStagingRoom( const gsi_char* pName, int maxPlayers, const gsi_char* pPassword, peerJoinRoomCallback	cb, void* pParam )
{
	SCreateStagingRoomOp*	pNewRoomOp = new SCreateStagingRoomOp();
	bool									success = false;

	if ( pNewRoomOp )
	{
		pNewRoomOp->m_pName = new char[ strlen( pName ) + 1 ];

		if ( pNewRoomOp->m_pName )
		{
			pNewRoomOp->m_opType = eROT_Create;
			pNewRoomOp->m_roomType = StagingRoom;
			pNewRoomOp->m_state = eTS_NotStarted;
			pNewRoomOp->m_cb = cb;
			pNewRoomOp->m_pParam = pParam;
			pNewRoomOp->m_maxPlayers = maxPlayers;
			strcpy( pNewRoomOp->m_pName, pName );
			pNewRoomOp->m_password[ 0 ] = '\0';

			if ( pPassword )
			{
				strncpy( pNewRoomOp->m_password, pPassword, sizeof ( pNewRoomOp->m_password ) );
				pNewRoomOp->m_password[ sizeof ( pNewRoomOp->m_password ) - 1 ] = '\0';
			}
		}
		else
		{
			SAFE_DELETE( pNewRoomOp );
		}
	}

	if ( pNewRoomOp )
	{
		m_roomOpQueue.push( pNewRoomOp );
	}
	else
	{
		if ( cb )
		{
			cb( m_peer, PEERFalse, PEERJoinFailed, StagingRoom, pParam );
		}
	}
}


// Description:
//	 Join a staging room.
// Arguments:
//	 server - server
//	 pPassword - password
//	 cb - callback
//	 pParam - callback parameter
void CCryGameSpyLobbyService::PeerJoinStagingRoom( SBServer server, const gsi_char* pPassword, peerJoinRoomCallback cb, void* pParam )
{
	SJoinStagingRoomOp*		pNewRoomOp = new SJoinStagingRoomOp();

	if ( pNewRoomOp )
	{
		pNewRoomOp->m_opType = eROT_Join;
		pNewRoomOp->m_roomType = StagingRoom;
		pNewRoomOp->m_state = eTS_NotStarted;
		pNewRoomOp->m_cb = cb;
		pNewRoomOp->m_pParam = pParam;
		pNewRoomOp->m_server = server;
		pNewRoomOp->m_password[ 0 ] = '\0';

		if ( pPassword )
		{
			strncpy( pNewRoomOp->m_password, pPassword, sizeof ( pNewRoomOp->m_password ) );
			pNewRoomOp->m_password[ sizeof ( pNewRoomOp->m_password ) - 1 ] = '\0';
		}

		m_roomOpQueue.push( pNewRoomOp );
	}
	else
	{
		if ( cb )
		{
			cb( m_peer, PEERFalse, PEERJoinFailed, StagingRoom, pParam );
		}
	}
}


// Description:
//	 Leave the current staging room.
void CCryGameSpyLobbyService::PeerLeaveStagingRoom( peerLeaveRoomCallback cb, void* pParam )
{
	SRoomOp*								pNewRoomOp = new SRoomOp();

	if ( pNewRoomOp )
	{
		pNewRoomOp->m_opType = eROT_Leave;
		pNewRoomOp->m_roomType = StagingRoom;
		pNewRoomOp->m_state = eTS_NotStarted;
		pNewRoomOp->m_cb = cb;
		pNewRoomOp->m_pParam = pParam;
		m_roomOpQueue.push( pNewRoomOp );
	}
	else
	{
		NetLogAlways( "[GameSpy] CCryGameSpyLobbyService::PeerLeaveStagingRoom: out of memory");
	}
}


// Description:
//	 Start listing games.
// Arguments:
//	 pFields - fields
//	 numFields - number of fields
//	 pFilter - filter
//	 callback - callback
//	 pParam - callback parameter
// Return:
//	 eCLE_Success or an error code
ECryLobbyError CCryGameSpyLobbyService::PeerStartListingGames( const unsigned char*	pFields, int numFields, const gsi_char* pFilter, peerListingGamesCallback callback, void* pParam )
{
	ECryLobbyError result = eCLE_ObjectNotConnected;

	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		if ( !m_peerSearching )
		{
			result = eCLE_Success;
			peerStartListingGames( m_peer, pFields, numFields, pFilter, callback, pParam );
			m_peerSearching = true;

			// That peerStartListingGames invalidated every SBServer pointer.
			
			SRoomOpPtr			pCurrent = NULL;
			SRoomOpPtr			pKeep = NULL;
			SRoomOpPtr			pSentinel = NULL;

			do
			{
				if ( pKeep )
				{
					if ( !pSentinel )
					{
						pSentinel = pKeep;
					}

					m_roomOpQueue.push( pKeep );
					pKeep = NULL;
				}

				if ( ( !m_roomOpQueue.empty() ) && ( m_roomOpQueue.front() != pSentinel ) )
				{
					pCurrent = m_roomOpQueue.front();
					m_roomOpQueue.pop();

					if ( ( pCurrent->m_opType == eROT_Join ) && ( pCurrent->m_roomType == StagingRoom ) )
					{
						SJoinStagingRoomOp*	pJoinStagingRoom = static_cast< SJoinStagingRoomOp* >( pCurrent.get() );

						if ( pJoinStagingRoom->m_cb )
						{
							static_cast< peerJoinRoomCallback >( pJoinStagingRoom->m_cb )( m_peer, PEERFalse, PEERJoinFailed, StagingRoom, pJoinStagingRoom->m_pParam );
						}
					}
					else
					{
						pKeep = pCurrent;
					}
				}
				else
				{
					pCurrent = NULL;
				}
			} while ( pCurrent );
		}
		else
		{
			result = eCLE_SystemIsBusy;
		}
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::PeerStartListingGames called with peer not in connected state" );
	}

	return result;
}


void CCryGameSpyLobbyService::PeerStopListingGames()
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		peerStopListingGames( m_peer );
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::PeerStopListingGames called with peer not in connected state" );
	}
	m_peerSearching = false;
}


void CCryGameSpyLobbyService::PeerUTMStagingRoom( const gsi_char* pCmd, const gsi_char* pParams )
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		peerUTMRoom( m_peer, StagingRoom, pCmd, pParams, PEERFalse );
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::PeerUTMStagingRoom called with peer not in connected state" );
	}
}


void CCryGameSpyLobbyService::PeerUTMPlayer( const gsi_char* pNick, const gsi_char* pCmd, const gsi_char* pParams )
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		peerUTMPlayer( m_peer, pNick, pCmd, pParams, PEERFalse );
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::PeerUTMPlayer called with peer not in connected state" );
	}
}


// Description:
//	 Shut down the peer.
void CCryGameSpyLobbyService::PeerShutdown()
{
	if(m_peer != GAMESPY_INVALID_PEER)
	{
		peerShutdown( m_peer );
	}

	m_peer = GAMESPY_INVALID_PEER;

	if ( m_pMatchMaking )
	{
		m_pMatchMaking->ClearTaskLinks();
	}

	SetGameSpyObjectState( eGSO_Peer, eCGSOS_NotInitialized, eCLE_Success );
}


void CCryGameSpyLobbyService::PeerStartGame()
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		peerStartGame( m_peer, NULL, PEER_KEEP_REPORTING | PEER_REPORT_PLAYERS );
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::PeerStartGame called with peer not in connected state" );
	}
}


void CCryGameSpyLobbyService::PeerStopGame()
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		peerStopGame( m_peer );
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::PeerStopGame called with peer not in connected state" );
	}
}

void CCryGameSpyLobbyService::PeerSendNatNegotiateCookie(uint32 ip, uint16 port, uint32 cookie)
{
	peerSendNatNegotiateCookie(m_peer, ip, port, cookie);
}

void CCryGameSpyLobbyService::PeerStateChanged()
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		peerStateChanged( m_peer );
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::PeerStateChanged called with peer not in connected state" );
	}
}


void CCryGameSpyLobbyService::PeerSetStagingRoomMaxPlayers( uint32 maxPlayers )
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		peerSetStagingRoomMaxPlayers( m_peer, maxPlayers );
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::PeerSetStagingRoomMaxPlayers called with peer not in connected state" );
	}
}


GPResult CCryGameSpyLobbyService::GPGetInfo( GPProfile profile, GPEnum checkCache, GPCallback callback, void* pParam )
{
	GPResult	result;

	if ( m_states[ eGSO_GPConnection ] == eCGSOS_Connected )
	{
		result = gpGetInfo( &m_GPConnection, profile, checkCache, GP_NON_BLOCKING, callback, pParam );
	}
	else
	{
		NetLog( "[GameSpy] CCryGameSpyLobbyService::GPGetInfo called with GPConnection not in connected state" );
		result = GP_NETWORK_ERROR;
	}

	return result;
}


// Description:
//	 Do we have the local user name yet?
// Return:
//	 True if we have the local user name, otherwise false.
bool CCryGameSpyLobbyService::HasLocalUserName()
{
	return m_uniqueNick.size() > 0;
}


// Description:
//	 Do we have the public address yet?
// Return:
//	 True if we have the public address, otherwise false.
bool CCryGameSpyLobbyService::HasPublicAddress()
{
	return m_havePublicAddress;
}


uint32 CCryGameSpyLobbyService::GetPublicIP()
{
	uint32					result;

	if ( m_havePublicAddress )
	{
		result = m_peerPublicIP;
	}
	else
	{
		result = 0;
	}

	return result;
}


uint16 CCryGameSpyLobbyService::GetPublicPort()
{
	uint16					result;

	if ( m_havePublicAddress )
	{
		result = m_peerPublicPort;
	}
	else
	{
		result = 0;
	}

	return result;
}

uint32 CCryGameSpyLobbyService::GetPrivateIP()
{
	uint32	ip = 0;

	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		ip = peerGetPrivateIP( m_peer );
	}

	return ip;
}

uint16 CCryGameSpyLobbyService::GetPrivatePort()
{
	uint32 ip = GetPrivateIP();

	if (ip != 0)
	{
		TNetAddressVec			addrVec;
		sockaddr_in					addr;
		IDatagramSocketPtr	pSocket;

		pSocket = m_pLobby->GetInternalSocket( m_service );

		if ( pSocket )
		{
			pSocket->GetSocketAddresses( addrVec );

			for ( int i = 0; i < addrVec.size(); ++i )
			{
				if ( ConvertAddr( addrVec[ i ], &addr ) )
				{
					if (addr.sin_addr.S_un.S_addr == ip)
					{
						return addr.sin_port;
					}
				}
			}
		}
	}

	return 0;
}

GPProfile CCryGameSpyLobbyService::GetProfileID()
{
	return m_profileId;
}


void CCryGameSpyLobbyService::OnInternalSocketChanged( IDatagramSocketPtr pSocket )
{
	m_havePublicAddress = false;
}

ECryLobbyError CCryGameSpyLobbyService::GetErrorFromGameSpy(GPResult error, GPConnection* pGPConnection)
{
	char errorString[GP_ERROR_STRING_LEN] = "Unknown error";
	GPErrorCode errorCode = GP_GENERAL;

	switch (error)
	{
	case GP_NO_ERROR:
		return eCLE_Success;

	case GP_MEMORY_ERROR:
		return eCLE_OutOfMemory;

	case GP_PARAMETER_ERROR:
		return eCLE_GP_ParameterError;

	case GP_NETWORK_ERROR:
		return eCLE_GP_NetworkError;

	case GP_SERVER_ERROR:
		return eCLE_ServerUnreachable;

	default:
		if (pGPConnection != NULL)
		{
			if (gpGetErrorCode(pGPConnection, &errorCode) == GP_NO_ERROR)
			{
				gpGetErrorString(pGPConnection, errorString);
			}
		}

		NetLog("[Lobby] eCLE_InternalError : Gamespy error %i - %s", errorCode, errorString);
		return eCLE_InternalError;
	}
}

void CCryGameSpyLobbyService::InviteAccepted(uint32 user, CrySessionID id)
{
	m_pLobby->InviteAccepted(m_service, user, id, eCLE_Success);
}

void CCryGameSpyLobbyService::InviteAcceptedAcknowledgement(int cookie)
{
	PEER peer = NULL;
	PeerQRNatNegotiateCallback(peer, cookie, this);
}

void CCryGameSpyLobbyService::DispatchOnlineStatus( ECryLobbySystemEvent event, EOnlineState eState, ECryLobbyError eReason )
{
	if (m_pLobby)
	{
		UCryLobbyEventData data;
		SCryLobbyOnlineStateData stateData;
		data.pOnlineStateData = &stateData;

		stateData.m_user = 0;
		stateData.m_curState = eState;
		stateData.m_reason = eReason;
		stateData.m_serviceConnected = (eState == eOS_SignedIn);

		m_pLobby->DispatchEvent( event, data );
	}
}

void CCryGameSpyLobbyService::GameSpyConnectCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*	pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPConnectResponseArg*			pConnectResponseArg = static_cast< GPConnectResponseArg* >( pArg );
	GPResult									result;
	ECryLobbyError						reason = eCLE_Success;

	if ( pConnectResponseArg )
	{
		result = pConnectResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	CRY_ASSERT_MESSAGE(pArg != NULL,"Gamespy passed us a null arg.. ");

	if ( result == GP_NO_ERROR )
	{
		pThis->m_profileId = pConnectResponseArg->profile;

		GPResult result = gpGetLoginTicket( pConnection, pThis->m_loginTicket );
		CRY_ASSERT_MESSAGE(result == GP_NO_ERROR,"Error retrieving login ticket");

		if ( pThis->m_pStats )
		{
			pThis->m_pStats->SetProfile( pThis->m_profileId, pThis->m_loginTicket );
		}

		pThis->GameSpyConnectSuccess( pConnection );
	}
	else
	{
		GPErrorCode								error;

		gpGetErrorCode( pConnection, &error );

		switch ( error )
		{
		case GP_LOGIN_TIMEOUT:
			// FALL THROUGH
		case GP_LOGIN_CONNECTION_FAILED:
			reason = eCLE_ServerUnreachable;
			break;
		case GP_LOGIN_BAD_PASSWORD:
			reason = eCLE_InvalidLoginCredentials;
			break;
		case GP_LOGIN_BAD_PROFILE:
			// FALL THROUGH
		case GP_LOGIN_PROFILE_DELETED:
			reason = eCLE_OtherUsersAccount;
			break;
		case GP_LOGIN_BAD_UNIQUENICK:
			reason = eCLE_InvalidLoginCredentials;
			break;
		case GP_LOGIN_BAD_EMAIL:
			// Don't know why this happened, the error codes refer to credentials that
			// we didn't use to get here.
			// FALL THROUGH
		case GP_LOGIN_BAD_NICK:
			// FALL THROUGH
		case GP_LOGIN_SERVER_AUTH_FAILED:
			// FALL THROUGH
		case GP_LOGIN_BAD_PREAUTH:
			// FALL THROUGH
		case GP_LOGIN_BAD_LOGIN_TICKET:
			// FALL THROUGH
		case GP_LOGIN_EXPIRED_LOGIN_TICKET:
			// FALL THROUGH
		default:
			break;
		}
	}

	if ( reason == eCLE_Success )
	{
		switch ( result )
		{
		case GP_NO_ERROR:
			break;
		case GP_MEMORY_ERROR:
			reason = eCLE_OutOfMemory;
			break;
		case GP_PARAMETER_ERROR:
			reason = eCLE_InvalidParam;
			break;
		case GP_NETWORK_ERROR:
			// FALL THROUGH
		case GP_SERVER_ERROR:
			// FALL THROUGH
		default:
			reason = eCLE_InternalError;
			break;
		}
	}

	if ( reason != eCLE_Success )
	{
		pThis->SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, reason );
	}
}

void CCryGameSpyLobbyService::GameSpyConnectSuccess( GPConnection *pConnection )
{
	SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Connected, eCLE_Success );

	if ( m_pFriends != NULL)
	{
		m_pFriends->LateInitialise( m_profileId );

		if ( m_pFriendsManagement != NULL )
		{
			m_pFriendsManagement->LateInitialise( pConnection, m_profileId, m_pFriends );
		}
	}
}

void CCryGameSpyLobbyService::GameSpyCreateProfileSearchCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPProfileSearchResponseArg*	pProfileSearchResponseArg = static_cast< GPProfileSearchResponseArg* >( pArg );
	GPResult										result;

	if ( pProfileSearchResponseArg )
	{
		result = pProfileSearchResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	if ( result == GP_NO_ERROR )
	{
		for ( uint32 i = 0; ( i < pProfileSearchResponseArg->numMatches ) && ( pThis->m_bestProfileQuality < ePMQ_NamespaceAndRequiredNickProfile ); ++i )
		{
			bool		isRequiredNick = !strcmp( pProfileSearchResponseArg->matches[ i ].nick, pThis->m_requiredNick.c_str() );
			bool		isNamespace = false;
			int32		quality = ePMQ_AnyProfile;

			if ( pProfileSearchResponseArg->matches[ i ].namespaceID == pThis->m_namespaceID )
			{
				if ( *pProfileSearchResponseArg->matches[ i ].uniquenick )
				{
					if ( strcmp( pProfileSearchResponseArg->matches[ i ].uniquenick, "@unregistered" ) )
					{
						isNamespace = true;
					}
				}
			}

			if ( isRequiredNick )
			{
				++quality;

				if ( isNamespace )
				{
					++quality;
				}
			}

			if ( quality > pThis->m_bestProfileQuality )
			{
				pThis->m_bestProfileQuality = quality;
				pThis->m_bestProfileNick = pProfileSearchResponseArg->matches[ i ].nick;

				if ( isNamespace )
				{
					pThis->m_bestProfileUniqueNick = pProfileSearchResponseArg->matches[ i ].uniquenick;
				}
				else
				{
					pThis->m_bestProfileUniqueNick.clear();
				}
			}
		}
	}

	if ( pProfileSearchResponseArg->more == GP_MORE )
	{
		if ( ( pThis->m_bestProfileQuality == ePMQ_NamespaceAndRequiredNickProfile ) || ( result != GP_NO_ERROR ) )
		{
			pProfileSearchResponseArg->more = GP_DONE;
		}
	}

	if ( pProfileSearchResponseArg->more == GP_DONE )
	{
		if ( result == GP_NO_ERROR )
		{
			switch ( pThis->m_bestProfileQuality )
			{
			case ePMQ_NoProfile:
				result = gpNewUser( pConnection, pThis->m_requiredNick.c_str(), NULL, pThis->m_email.c_str(), pThis->m_password.c_str(), NULL, GP_NON_BLOCKING, GameSpyCreateNewUserCallback, pThis );
				break;
			case ePMQ_AnyProfile:
				pThis->m_workQueue.Add( &CCryGameSpyLobbyService::GameSpyCreateConnect, pThis, pConnection, pThis->m_bestProfileNick.c_str(), GameSpyCreateConnectAnyCallback );
				break;
			case ePMQ_RequiredNickProfile:
				pThis->m_workQueue.Add( &CCryGameSpyLobbyService::GameSpyCreateConnect, pThis, pConnection, pThis->m_requiredNick.c_str(), GameSpyCreateConnectRequiredCallback );
				break;
			case ePMQ_NamespaceAndRequiredNickProfile:
				result = gpCheckUser( pConnection, pThis->m_requiredNick.c_str(), pThis->m_email.c_str(), pThis->m_password.c_str(), GP_NON_BLOCKING, GameSpyCreateCheckUserCallback, pThis );
				break;
			}
		}

		switch ( result )
		{
		case GP_NO_ERROR:
			break;
		case GP_MEMORY_ERROR:
			pThis->SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_OutOfMemory );
			break;
		case GP_PARAMETER_ERROR:
			pThis->SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_InvalidParam );
			break;
		case GP_NETWORK_ERROR:
			// FALL THROUGH
		case GP_SERVER_ERROR:
			pThis->SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_ServerUnreachable );
			break;
		default:
			pThis->SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_InternalError );
			break;
		}
	}
}

void CCryGameSpyLobbyService::GameSpyCreateConnect( GPConnection* pConnection, const char* pProfileNick, GPCallback pCb )
{
	GPResult							result;

	result = gpConnect( pConnection, pProfileNick, m_email.c_str(), m_password.c_str(), GP_NO_FIREWALL, GP_NON_BLOCKING, pCb, this );

	switch ( result )
	{
	case GP_NO_ERROR:
		break;
	case GP_MEMORY_ERROR:
		SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_OutOfMemory );
		break;
	case GP_PARAMETER_ERROR:
		SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_InvalidParam );
		break;
	case GP_NETWORK_ERROR:
		// FALL THROUGH
	case GP_SERVER_ERROR:
		SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_ServerUnreachable );
		break;
	default:
		SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_InternalError );
		break;
	}
}

void CCryGameSpyLobbyService::GameSpyCreateNewUserCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPNewUserResponseArg*				pNewUserResponseArg = static_cast< GPNewUserResponseArg* >( pArg );
	GPResult										result;
	ECryLobbyError							reason = eCLE_Success;

	if ( pNewUserResponseArg )
	{
		result = pNewUserResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	if ( result == GP_NO_ERROR )
	{
		pThis->m_workQueue.Add( &CCryGameSpyLobbyService::GameSpyCreateConnect, pThis, pConnection, pThis->m_requiredNick.c_str(), GameSpyCreateConnectRequiredCallback );
	}
	else
	{
		GPErrorCode									error;

		gpGetErrorCode( pConnection, &error );

		switch ( error )
		{
		case GP_NEWUSER_UNIQUENICK_INUSE:
			// FALL THROUGH
		case GP_NEWUSER_UNIQUENICK_INVALID:
			result = gpSuggestUniqueNick( pConnection, pThis->m_uniqueNick.c_str(), GP_NON_BLOCKING, GameSpyCreateSuggestUniqueNicksDontDisconnectCallback, pThis );
			break;
		case GP_NEWUSER_BAD_PASSWORD:
			// In the time between the search confirming that the account didn't exist
			// and the attempt to create the account, someone else created an account
			// with the same email address ...
			// FALL THROUGH
		case GP_NEWUSER_BAD_NICK:
			// ... and in this case, even the password was the same, but  we have to
			// pretend that the user got the password wrong, because to do otherwise
			// would allow the user to steal another user's account.
			reason = eCLE_OtherUsersAccount;
			break;
		}
	}

	if ( reason == eCLE_Success )
	{
		switch ( result )
		{
		case GP_NO_ERROR:
			break;
		case GP_MEMORY_ERROR:
			reason = eCLE_OutOfMemory;
			break;
		case GP_PARAMETER_ERROR:
			reason = eCLE_InvalidParam;
			break;
		case GP_NETWORK_ERROR:
			// FALL THROUGH
		case GP_SERVER_ERROR:
			reason = eCLE_ServerUnreachable;
			break;
		default:
			reason = eCLE_InternalError;
			break;
		}
	}

	if ( reason != eCLE_Success )
	{
		pThis->SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, reason );
	}
}

void CCryGameSpyLobbyService::GameSpyCreateConnectAnyCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPConnectResponseArg*				pConnectResponseArg = static_cast< GPConnectResponseArg* >( pArg );
	GPResult										result;
	bool												disconnectOnError = false;

	if ( pConnectResponseArg )
	{
		result = pConnectResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	if ( result == GP_NO_ERROR )
	{
		disconnectOnError = true;
		result = gpNewProfile( pConnection, pThis->m_requiredNick.c_str(), GP_DONT_REPLACE, GP_NON_BLOCKING, GameSpyCreateNewProfileCallback, pThis );
	}

	pThis->GameSpyCreateConnectError( pConnection, result, disconnectOnError );
}

void CCryGameSpyLobbyService::GameSpyCreateConnectRequiredCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPConnectResponseArg*				pConnectResponseArg = static_cast< GPConnectResponseArg* >( pArg );
	GPResult										result;
	bool												disconnectOnError = false;

	if ( pConnectResponseArg )
	{
		result = pConnectResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	if ( result == GP_NO_ERROR )
	{
		disconnectOnError = true;
		pThis->m_profileId = pConnectResponseArg->profile;
		result = gpGetLoginTicket( pConnection, pThis->m_loginTicket );
		CRY_ASSERT_MESSAGE( result == GP_NO_ERROR, "Error retrieving login ticket" );

		if ( result == GP_NO_ERROR )
		{
			if ( pThis->m_pStats )
			{
				pThis->m_pStats->SetProfile(pThis-> m_profileId, pThis->m_loginTicket );

				ECryLobbyError							error = pThis->CheckProfanity( pThis->m_uniqueNick.c_str(), NULL, GameSpyCreateCheckProfanityCallback, pThis );

				if ( error != eCLE_Success )
				{
					result = GP_MISC_ERROR;
				}
			}
			else
			{
				pThis->m_workQueue.Add( &CCryGameSpyLobbyService::GameSpyCreateRegisterUniqueNick, pThis );
			}
		}
	}

	pThis->GameSpyCreateConnectError( pConnection, result, disconnectOnError );
}

void CCryGameSpyLobbyService::GameSpyCreateCheckProfanityCallback( CryLobbyTaskID taskID, ECryLobbyError error, const char* pString, bool isProfanity, void* pArg )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pArg );

	if ( isProfanity )
	{
		FROM_GAME_TO_LOBBY( &CCryGameSpyLobbyService::GameSpyDisconnect, pThis, eCLE_OnlineNameRejected );
	}
	else
	{
		FROM_GAME_TO_LOBBY( &CCryGameSpyLobbyService::GameSpyCreateRegisterUniqueNick, pThis );
	}
}

void CCryGameSpyLobbyService::GameSpyCreateRegisterUniqueNick()
{
	GPResult										result;
	ECryLobbyError							reason = eCLE_Success;

	result = gpRegisterUniqueNick( &m_GPConnection, m_uniqueNick.c_str(), NULL, GP_NON_BLOCKING, GameSpyCreateRegisterUniqueNickCallback, this );

	switch ( result )
	{
	case GP_NO_ERROR:
		break;
	case GP_MEMORY_ERROR:
		reason = eCLE_OutOfMemory;
		break;
	case GP_PARAMETER_ERROR:
		reason = eCLE_InvalidParam;
		break;
	case GP_NETWORK_ERROR:
		// FALL THROUGH
	case GP_SERVER_ERROR:
		// FALL THROUGH
	default:
		reason = eCLE_InternalError;
		break;
	}

	if ( reason != eCLE_Success )
	{
		GameSpyDisconnect( reason );
	}
}

void CCryGameSpyLobbyService::GameSpyCreateConnectError( GPConnection* pConnection, GPResult result, bool disconnectOnError )
{
	ECryLobbyError							reason = eCLE_Success;

	if ( result != GP_NO_ERROR )
	{
		GPErrorCode									error;

		gpGetErrorCode( pConnection, &error );

		switch ( error )
		{
		case GP_LOGIN_TIMEOUT:
			// FALL THROUGH
		case GP_LOGIN_CONNECTION_FAILED:
			reason = eCLE_ServerUnreachable;
			break;
		case GP_LOGIN_BAD_EMAIL:
			// Someone just deleted the account, not a good idea to try to use it.
			// FALL THROUGH
		case GP_LOGIN_BAD_NICK:
			// Someone just deleted the profile, not a good idea to try to use this account.
			// FALL THROUGH
		case GP_LOGIN_BAD_PASSWORD:
			// FALL THROUGH
		case GP_LOGIN_BAD_PROFILE:
			// FALL THROUGH
		case GP_LOGIN_PROFILE_DELETED:
			reason = eCLE_OtherUsersAccount;
			break;
		case GP_LOGIN_SERVER_AUTH_FAILED:
			// Don't know why this happened, the error codes refer to credentials that
			// we didn't use to get here.
			// FALL THROUGH
		case GP_LOGIN_BAD_UNIQUENICK:
			// FALL THROUGH
		case GP_LOGIN_BAD_PREAUTH:
			// FALL THROUGH
		case GP_LOGIN_BAD_LOGIN_TICKET:
			// FALL THROUGH
		case GP_LOGIN_EXPIRED_LOGIN_TICKET:
			// FALL THROUGH
		default:
			break;
		}
	}

	if ( reason == eCLE_Success )
	{
		switch ( result )
		{
		case GP_NO_ERROR:
			break;
		case GP_MEMORY_ERROR:
			reason = eCLE_OutOfMemory;
			break;
		case GP_PARAMETER_ERROR:
			reason = eCLE_InvalidParam;
			break;
		case GP_NETWORK_ERROR:
			// FALL THROUGH
		case GP_SERVER_ERROR:
			// FALL THROUGH
		default:
			reason = eCLE_InternalError;
			break;
		}
	}

	if ( reason != eCLE_Success )
	{
		if ( disconnectOnError )
		{
			m_workQueue.Add( &CCryGameSpyLobbyService::GameSpyDisconnect, this, reason );
		}
		else
		{
			SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, reason );
		}
	}
}

void CCryGameSpyLobbyService::GameSpyCreateCheckUserCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPCheckResponseArg*					pCheckUserResponseArg = static_cast< GPCheckResponseArg* >( pArg );
	GPResult										result;
	ECryLobbyError							reason = eCLE_Success;

	if ( pCheckUserResponseArg )
	{
		result = pCheckUserResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	if ( result == GP_NO_ERROR )
	{
		if ( pThis->m_uniqueNick != pThis->m_bestProfileUniqueNick )
		{
			char**										ppUniqueNick = new char*[ 1 ];

			if ( ppUniqueNick )
			{
				*ppUniqueNick = new char[ pThis->m_bestProfileUniqueNick.length() + 1 ];
				TO_GAME_FROM_LOBBY( &CCryGameSpyLobbyService::DispatchNickRejected, pThis, 1U, ppUniqueNick, false, eCLE_Success );
			}
		}

		pThis->m_requireNewCredentials = true;
		reason = eCLE_ThisUsersAccount;
	}
	else
	{
		GPErrorCode									error;

		gpGetErrorCode( pConnection, &error );

		switch ( error )
		{
		case GP_CHECK_BAD_EMAIL:
			// FALL THROUGH
		case GP_CHECK_BAD_NICK:
			// FALL THROUGH
		case GP_CHECK_BAD_PASSWORD:
			reason = eCLE_OtherUsersAccount;
			break;
		default:
			break;
		}
	}

	if ( reason == eCLE_Success )
	{
		switch ( result )
		{
		case GP_MEMORY_ERROR:
			reason = eCLE_OutOfMemory;
			break;
		case GP_PARAMETER_ERROR:
			reason = eCLE_InvalidParam;
			break;
		case GP_NETWORK_ERROR:
			// FALL THROUGH
		case GP_SERVER_ERROR:
			reason = eCLE_ServerUnreachable;
			break;
		default:
			reason = eCLE_InternalError;
			break;
		}
	}

	if ( reason != eCLE_Success )
	{
		pThis->SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, reason );
	}
}

void CCryGameSpyLobbyService::GameSpyCreateNewProfileCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPNewProfileResponseArg*		pNewProfileResponseArg = static_cast< GPNewProfileResponseArg* >( pArg );
	GPResult										result;

	if ( pNewProfileResponseArg )
	{
		result = pNewProfileResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	pThis->m_workQueue.Add( &CCryGameSpyLobbyService::GameSpyCreateReconnect, pThis, pConnection, result );
}

void CCryGameSpyLobbyService::GameSpyCreateReconnect( GPConnection* pConnection, GPResult result )
{
	ECryLobbyError							reason = eCLE_Success;

	gpDisconnect( pConnection );

	if ( result == GP_NO_ERROR )
	{
		result = gpConnect( pConnection, m_requiredNick.c_str(), m_email.c_str(), m_password.c_str(), GP_NO_FIREWALL, GP_NON_BLOCKING, GameSpyCreateConnectRequiredCallback, this );
	}
	else
	{
		GPErrorCode									error;

		gpGetErrorCode( pConnection, &error );

		switch ( error )
		{
		case GP_NEWPROFILE_BAD_NICK:
			// FALL THROUGH
		case GP_NEWPROFILE_BAD_OLD_NICK:
			// Someone else just created the profile, not a good idea to use this account.
			reason = eCLE_OtherUsersAccount;
			break;
		}
	}

	if ( reason == eCLE_Success )
	{
		switch ( result )
		{
		case GP_NO_ERROR:
			break;
		case GP_MEMORY_ERROR:
			reason = eCLE_OutOfMemory;
			break;
		case GP_PARAMETER_ERROR:
			reason = eCLE_InvalidParam;
			break;
		case GP_NETWORK_ERROR:
			// FALL THROUGH
		case GP_SERVER_ERROR:
			reason = eCLE_ServerUnreachable;
			break;
		default:
			reason = eCLE_InternalError;
			break;
		}
	}

	if ( reason != eCLE_Success )
	{
		SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_OtherUsersAccount );
	}
}

void CCryGameSpyLobbyService::GameSpyCreateRegisterUniqueNickCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*					pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPRegisterUniqueNickResponseArg*	pRegisterUniqueNickResponseArg = static_cast< GPRegisterUniqueNickResponseArg* >( pArg );
	GPResult													result;
	ECryLobbyError										reason = eCLE_Success;

	if ( pRegisterUniqueNickResponseArg )
	{
		result = pRegisterUniqueNickResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	if ( result == GP_NO_ERROR )
	{
		pThis->GameSpyConnectSuccess( pConnection );
	}
	else
	{
		GPErrorCode												error;

		gpGetErrorCode( pConnection, &error );

		switch ( error )
		{
		case GP_REGISTERUNIQUENICK_RESERVED:
			// FALL THROUGH
		case GP_REGISTERUNIQUENICK_TAKEN:
			result = gpSuggestUniqueNick( pConnection, pThis->m_uniqueNick.c_str(), GP_NON_BLOCKING, GameSpyCreateSuggestUniqueNicksDisconnectCallback, pThis );
			break;
		}
	}

	if ( reason == eCLE_Success )
	{
		switch ( result )
		{
		case GP_NO_ERROR:
			break;
		case GP_MEMORY_ERROR:
			reason = eCLE_OutOfMemory;
			break;
		case GP_PARAMETER_ERROR:
			reason = eCLE_InvalidParam;
			break;
		case GP_NETWORK_ERROR:
			// FALL THROUGH
		case GP_SERVER_ERROR:
			reason = eCLE_ServerUnreachable;
			break;
		default:
			reason = eCLE_InternalError;
			break;
		}
	}

	if ( reason != eCLE_Success )
	{
		pThis->m_requireNewCredentials = true;
		TO_GAME_FROM_LOBBY( &CCryGameSpyLobbyService::DispatchNickRejected, pThis, 0U, ( char** )NULL, true, reason );
	}
}

void CCryGameSpyLobbyService::GameSpyCreateSuggestUniqueNicksDisconnectCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	GameSpyCreateSuggestUniqueNicksCallback( pConnection, pArg, pParam, true );
}

void CCryGameSpyLobbyService::GameSpyCreateSuggestUniqueNicksDontDisconnectCallback( GPConnection* pConnection, void* pArg, void* pParam )
{
	GameSpyCreateSuggestUniqueNicksCallback( pConnection, pArg, pParam, false );
}

void CCryGameSpyLobbyService::GameSpyCreateSuggestUniqueNicksCallback( GPConnection* pConnection, void* pArg, void* pParam, bool disconnect )
{
	CCryGameSpyLobbyService*				pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPSuggestUniqueNickResponseArg*	pSuggestUniqueNickResponseArg = static_cast< GPSuggestUniqueNickResponseArg* >( pArg );
	GPResult												result;
	uint32													numNicks = 0;
	char**													ppNicks = NULL;
	ECryLobbyError									reason = eCLE_Success;

	if ( pSuggestUniqueNickResponseArg )
	{
		result = pSuggestUniqueNickResponseArg->result;
	}
	else
	{
		result = GP_MISC_ERROR;
	}

	switch ( result )
	{
	case GP_NO_ERROR:
		if ( pSuggestUniqueNickResponseArg->numSuggestedNicks )
		{
			ppNicks = new char*[ pSuggestUniqueNickResponseArg->numSuggestedNicks ];

			if ( ppNicks )
			{
				for ( uint32 i = 0; i < pSuggestUniqueNickResponseArg->numSuggestedNicks; ++i )
				{
					ppNicks[ numNicks ] = new char[ strlen( pSuggestUniqueNickResponseArg->suggestedNicks[ i ] ) + 1 ];

					if ( ppNicks[ numNicks ] )
					{
						strcpy( ppNicks[ numNicks ], pSuggestUniqueNickResponseArg->suggestedNicks[ i ] );
						++numNicks;
					}
				}
			}
		}

		reason = eCLE_OnlineNameRejected;
		
		break;
	case GP_MEMORY_ERROR:
		reason = eCLE_OutOfMemory;
		break;
	case GP_PARAMETER_ERROR:
		reason = eCLE_InvalidParam;
		break;
	case GP_NETWORK_ERROR:
		// FALL THROUGH
	case GP_SERVER_ERROR:
		reason = eCLE_ServerUnreachable;
		break;
	default:
		reason = eCLE_InternalError;
		break;
	}

	pThis->m_requireNewCredentials = true;
	TO_GAME_FROM_LOBBY( &CCryGameSpyLobbyService::DispatchNickRejected, pThis, numNicks, ppNicks, disconnect, reason );
}

void CCryGameSpyLobbyService::GameSpyConnect()
{
	ECryLobbyLoginGUIState	state = GetLoginCredentials();

	if ( state != eCLLGS_NotFinished )
	{
		SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Connecting, eCLE_Success );
	}

	if ( m_requireNewCredentials )
	{
		SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_UserNotSignedIn );
	}
	else
	{
		GPResult result = GP_NO_ERROR;

		switch ( state )
		{
		case eCLLGS_NotFinished:

			break;

		case eCLLGS_Cancelled:

			SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_Cancelled );
			break;

		case eCLLGS_PasswordIncorrect:

			SetGameSpyObjectState(eGSO_GPConnection, eCGSOS_Initialized, eCLE_PasswordIncorrect);
			break;

		case eCLLGS_NewAccount:

			m_bestProfileNick.clear();
			m_bestProfileUniqueNick.clear();
			m_bestProfileQuality = ePMQ_NoProfile;
			result = gpProfileSearch( &m_GPConnection, NULL, NULL, m_email.c_str(), NULL, NULL, 0, GP_NON_BLOCKING, GameSpyCreateProfileSearchCallback, this );
			break;

		case eCLLGS_NotImplemented:
			// FALL THROUGH
		case eCLLGS_ExistingAccount:

			result = gpConnectUniqueNick( &m_GPConnection, m_uniqueNick, m_password, GP_NO_FIREWALL, GP_NON_BLOCKING, GameSpyConnectCallback, this );
			break;

		case eCLLGS_InvalidUniqueNickSize:	// Intentional fall-through
		case eCLLGS_InvalidUniqueNickChar:
			result = GP_PARAMETER_ERROR;
			break;
		}

		switch ( result )
		{
		case GP_NO_ERROR:
			break;
		case GP_MEMORY_ERROR:
			SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_OutOfMemory );
			break;
		case GP_PARAMETER_ERROR:
			SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_InvalidParam );
			break;
		case GP_NETWORK_ERROR:
			// FALL THROUGH
		case GP_SERVER_ERROR:
			SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_ServerUnreachable );
			break;
		default:
			SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_InternalError );
			break;
		}

		CRY_ASSERT_MESSAGE(result == GP_NO_ERROR,"Failed to connect to gamespy service");
	}
}

void CCryGameSpyLobbyService::GameSpyDisconnect( ECryLobbyError reason )
{
	gpDisconnect( &m_GPConnection );
	SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, reason );
}

// Description:
//	 Connect the peer.
void CCryGameSpyLobbyService::PeerConnect()
{
	ECryLobbyLoginGUIState	state = GetLoginCredentials();

	if ( m_states[ eGSO_GPConnection ] != eCGSOS_Connected )
	{
		return;
	}

	if ( state != eCLLGS_NotFinished )
	{
		SetGameSpyObjectState( eGSO_Peer, eCGSOS_Connecting, eCLE_Success );
	}

	if ( m_requireNewCredentials )
	{
		SetGameSpyObjectState( eGSO_Peer, eCGSOS_Initialized, eCLE_UserNotSignedIn );
	}
	else
	{
		switch ( state )
		{
		case eCLLGS_NotFinished:

			break;

		case eCLLGS_Cancelled:

			SetGameSpyObjectState( eGSO_Peer, eCGSOS_Initialized, eCLE_InvalidLoginCredentials );
			break;

		case eCLLGS_NewAccount:
			// FALL THROUGH
		case eCLLGS_NotImplemented:
			// FALL THROUGH
		case eCLLGS_ExistingAccount:

			peerConnectLogin( m_peer, m_namespaceID, NULL, NULL, m_uniqueNick, m_password, PeerNickErrorCallback, PeerConnectCallback, this, PEERFalse );
			break;
		}
	}
}


ECryLobbyLoginGUIState CCryGameSpyLobbyService::GetLoginCredentials()
{
	ECryLobbyLoginGUIState	result;

	SConfigurationParams	neededInfo[ 5 ] =
	{
		{ CLCC_CRYLOBBY_LOGINGUISTATE,	{ NULL } },
		{ CLCC_CRYLOBBY_LOGINGUICOUNT,	{ NULL } },
		{ CLCC_GAMESPY_EMAIL,						{ NULL } },
		{ CLCC_GAMESPY_UNIQUENICK,			{ NULL } },
		{ CLCC_GAMESPY_PASSWORD,				{ NULL } }
	};

	m_pLobby->GetConfigurationInformation( neededInfo, ARRAY_COUNT( neededInfo ) );
	result = static_cast< ECryLobbyLoginGUIState >( neededInfo[ 0 ].m_32 );

	if ( !( m_requireNewCredentials && ( neededInfo[ 1 ].m_32 == m_loginCount ) ) )
	{
		m_loginCount = neededInfo[ 1 ].m_32;
		m_requireNewCredentials = false;

		switch ( result )
		{
		case eCLLGS_NotImplemented:
			// FALL THROUGH
		case eCLLGS_NewAccount:
			// FALL THROUGH
		case eCLLGS_ExistingAccount:
			m_email = ( const char* )neededInfo[ 2 ].m_pData;
			m_uniqueNick = ( const char* )neededInfo[ 3 ].m_pData;
			m_password = ( const char* )neededInfo[ 4 ].m_pData;
			break;
		}

		if ( result != eCLLGS_NotFinished )
		{
			if (m_password.length() == 0)
			{
				result = eCLLGS_PasswordIncorrect;
			}
			else
			{
				// Validation of unique nick length, and that a password has been supplied
				uint32 uniqueNickLength = m_uniqueNick.length();

				if ((uniqueNickLength < GAMESPY_UNIQUENICK_MIN_LENGTH) || (uniqueNickLength > GAMESPY_UNIQUENICK_MAX_LENGTH))
				{
					result = eCLLGS_InvalidUniqueNickSize;
				}
				else
				{
					const char* nick = m_uniqueNick.c_str();

					// Validation of chars in unique nick
					for (uint32 i = 0; (result != eCLLGS_InvalidUniqueNickChar) && (i < uniqueNickLength); ++i)
					{
						if ((nick[i] < '\"') || (nick[i] > '~') || (nick[i] == '\\') || (nick[i] == ','))
						{
							result = eCLLGS_InvalidUniqueNickChar;
						}
					}

					if (result != eCLLGS_InvalidUniqueNickChar)
					{
						// Validation of first char of unique nick
						if (((nick[0] >= '0') && (nick[0] <= '9')) || (nick[0] == '@') || (nick[0] == '+') || (nick[0] == ':') || (nick[0] == '#'))
						{
							result = eCLLGS_InvalidUniqueNickChar;
						}
					}
				}
			}
		}
	}

	return result;
}


// Description:
//	 Get GameSpy matchmaking.
// Return:
//	 GameSpy matchmaking.
CCryGameSpyMatchMaking* CCryGameSpyLobbyService::GetGameSpyMatchMaking()
{
	return m_pMatchMaking;
}


// Description:
//	 Called when local player is disconnected.
// Arguments:
//	 peer - peer
//	 reason - reason for disconnection
//	 pParam - user data
void CCryGameSpyLobbyService::PeerDisconnectedCallback( PEER peer, const gsi_char* pReason, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	pService->SetGameSpyObjectState( eGSO_Peer, eCGSOS_Initialized, eCLE_ObjectNotConnected );
}


// Description:
//	 Called when chat message is received.
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pNick - nick of sender
//	 pMessage - message text
//	 messageType - type of message
//	 pParam - user data
void CCryGameSpyLobbyService::PeerRoomMessageCallback( PEER peer, RoomType roomType, const gsi_char* pNick, const gsi_char* pMessage, MessageType messageType, void* pParam )
{
	// Do nothing. Text chat isn't supported yet.
}


// Description:
//	 Called when an under-the-table message has arrived in a room the developer is in.
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pNick - nick of sender
//	 pCommand - command type of this UTM
//	 pParameters - payload or content of this UTM
//	 authenticated - true if this has been authenticated by the server
//	 pParam - user data
void CCryGameSpyLobbyService::PeerRoomUTMCallback( PEER peer, RoomType roomType, const gsi_char* pNick, const gsi_char* pCommand, const gsi_char* pParameters, PEERBool authenticated, void* pParam )
{
	// Do nothing. We haven't defined any peer to peer data transfers that use
	// UTM messages yet.
}


// Description:
//	 Called when the name of a room the developer is in has changed.
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pParam - user data
void CCryGameSpyLobbyService::PeerRoomNameChangedCallback( PEER peer, RoomType roomType, void* pParam )
{
	 // Do nothing. We're not doing anything with room names yet.
}


// Description:
//	 Called when the name of a room the developer is in has changed.
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pMode - current mode for this room
//	 pParam - user data
void CCryGameSpyLobbyService::PeerRoomModeChangedCallback( PEER peer, RoomType roomType, CHATChannelMode* pMode, void* pParam )
{
	// Do nothing. We're not doing anything with room modes yet.
}


// Description:
//	 Called when a message has arrived from another player.
// Arguments:
//	 peer - peer
//	 pNick - nick of sender
//	 pMessage - message text
//	 messageType - type of message
//	 pParam - user data
void CCryGameSpyLobbyService::PeerPlayerMessageCallback( PEER peer, const gsi_char* pNick, const gsi_char* pMessage, MessageType messageType, void* pParam )
{
	// Do nothing. Text chat isn't supported yet.
}


// Description:
//	 Called when an under-the-table message has arrived from another player.
// Arguments:
//	 peer - peer
//	 pNick - nick of sender
//	 pCommand - command type of this UTM
//	 pParameters - payload or content of this UTM
//	 authenticated - true if this has been authenticated by the server
//	 pParam - user data
void CCryGameSpyLobbyService::PeerPlayerUTMCallback( PEER peer, const gsi_char* pNick, const gsi_char* pCommand, const gsi_char* pParameters, PEERBool authenticated, void* pParam )
{
	// Do nothing. We haven't defined any peer to peer data transfers that use
	// UTM messages yet.
}


// Description:
//	 Called when an under-the-table message has arrived from another player.
// Arguments:
//	 peer - peer
//	 pNick - nick of the player who's ready state changed
//	 ready - player's new ready state
//	 pParam - user data
void CCryGameSpyLobbyService::PeerReadyChangedCallback( PEER peer, const gsi_char* pNick, PEERBool ready, void* pParam )
{
	// Do nothing. We're not doing anything with ready messages yet.
}


// Description:
//	 Called when the host in the staging room launches the game
// Arguments:
//	 peer - peer
//	 server - server object representing this host
//	 pMessage - message that was passed into peerStartGame
//	 pParam - user data
void CCryGameSpyLobbyService::PeerGameStartedCallback( PEER peer, SBServer server, const gsi_char* pMessage, void* pParam )
{
	// Do nothing. The host only does this to keep GameSpy's view of the staging room up to date.
}


// Description:
//	 Called when a player has joined one of the rooms the local player has joined
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pNick - nick of the player who joined
//	 pParam - user data
void
CCryGameSpyLobbyService::PeerPlayerJoinedCallback( PEER peer, RoomType roomType, const gsi_char* pNick, void* pParam )
{
	// Do nothing. Even if it's a staging room for which we're the host, the only reason another peer joined is to keep GameSpy in sync.
}


// Description:
//	 Called when a player has left one of the rooms the local player has joined
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pNick - nick of the player who left
//	 pReason - reason the player left
//	 pParam - user data
void CCryGameSpyLobbyService::PeerPlayerLeftCallback( PEER peer, RoomType roomType, const gsi_char* pNick, const gsi_char* pReason, void* pParam )
{
	// Do nothing. We only leave staging rooms to keep GameSpy in sync.
}


// Description:
//	 Called when the local player has been kicked from a room
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pNick - nick of the kicker
//	 pReason - reason for kick
//	 pParam - user data
void CCryGameSpyLobbyService::PeerKickedCallback( PEER peer, RoomType roomType, const gsi_char* pNick, const gsi_char* pReason, void* pParam )
{
	// Do nothing. We only kick peers to keep GameSpy in sync.
}


// Description:
//	 Called when the entire player list has been updated
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pParam - user data
void CCryGameSpyLobbyService::PeerNewPlayerListCallback( PEER peer, RoomType roomType, void* pParam )
{
	// Do nothing. Player list only updated to keep GameSpy in sync.
}


// Description:
//	 Called when player in one of the rooms changed his nick
// Arguments:
//	 peer - peer
//	 roomType - type of room
//	 pOldNick - old nick
//	 pNewNick - new nick
//	 pParam - user data
void CCryGameSpyLobbyService::PeerPlayerChangedNickCallback( PEER peer, RoomType roomType, const gsi_char* pOldNick, const gsi_char* pNewNick, void* pParam )
{
	// Do nothing. We're not doing anything with nick changes.
}


// Description:
//	 Called for all players (who are using peer) in a room shortly after joining
// Arguments:
//	 peer - peer
//	 roomType - room the info was gotten in
//	 pNick - nick of the player the info is for
//	 IP - player's IP
//	 profileID - player's profile ID
//	 pParam - user data
void CCryGameSpyLobbyService::PeerPlayerInfoCallback( PEER peer, RoomType roomType, const gsi_char* pNick, unsigned int IP, int profileID, void* pParam )
{
	// Do nothing. We only join staging rooms to keep GameSpy in sync.
}


// Description:
//	 Called when a player's flags have changed
// Arguments:
//	 peer - peer
//	 roomType - room the info was gotten in
//	 pNick - nick of the player the info is for
//	 oldFlags - player's old flags
//	 newFlags - player's new flags
//	 pParam - user data
void CCryGameSpyLobbyService::PeerPlayerFlagsChangedCallback( PEER peer, RoomType roomType, const gsi_char* pNick, int oldFlags, int newFlags, void* pParam )
{
	 // Do nothing. We're not using player flags.
}


// Description:
//	 Called when updated ping time is received.
// Arguments:
//	 peer - peer
//	 pNick - nick of the player the info is for
//	 ping - ping
//	 pParam - user data
void CCryGameSpyLobbyService::PeerPingCallback( PEER peer, const gsi_char* pNick, int ping, void* pParam )
{
	// Not recording ping data.
}


// Description:
//	 Called when updated cross-ping time is received.
// Arguments:
//	 peer - peer
//	 pNick1 - first player's nick
//	 pNick2 - second player's nick
//	 crossPing - cross-ping
//	 pParam - user data
void
CCryGameSpyLobbyService::PeerCrossPingCallback( PEER peer, const gsi_char* pNick1, const gsi_char* pNick2, int crossPing, void* pParam )
{
	// Not recording ping data.
}


// Description:
//	 Called when a global watch key changes value
// Arguments:
//	 peer - peer
//	 pNick - player's nick
//	 pKey - key
//	 pValue - value
//	 pParam - user data
void CCryGameSpyLobbyService::PeerGlobalKeyChangedCallback( PEER peer, const gsi_char* pNnick, const gsi_char* pKey, const gsi_char* pValue, void* pParam )
{
	// Do nothing. We're not using player global keys.
}


// Description:
//	 Called when a room watch key or a broadcast key changes value
// Arguments:
//	 peer - peer
//	 roomType - room the info was gotten in
//	 pNick - player's nick
//	 pKey - key
//	 pValue - value
//	 pParam - user data
void CCryGameSpyLobbyService::PeerRoomKeyChangedCallback( PEER peer, RoomType roomType, const gsi_char* pNick, const gsi_char* pKey, const gsi_char* pValue, void* pParam )
{
	// Do nothing. We only change room keys to keep GameSpy in sync.
}


#if !defined(PURE_CLIENT)
// Description:
//	 Called while you are hosting a game for you to report a game host key
//	 value, such as the name of the map being played
// Arguments:
//	 peer - peer
//	 key - key for which to report information
//	 buffer - fill in the information using this buffer
//	 pParam - user data
void CCryGameSpyLobbyService::PeerQRServerKeyCallback( PEER peer, int key, qr2_buffer_t buffer, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( pService )
	{
		if ( pService->m_reportingState == eRS_StagingRoomHost )
		{
			pService->m_pMatchMaking->QueryKey( key_server, key, 0, buffer );
		}
	}
}


// Description:
//	 Called while you are hosting a game for you to report the
//	 values of your player keys
// Arguments:
//	 peer - peer
//	 key - key for which to report information
//	 index - index of the player for which to report info
//	 buffer - fill in the information using this buffer
//	 pParam - user data
void CCryGameSpyLobbyService::PeerQRPlayerKeyCallback( PEER peer, int key, int index, qr2_buffer_t buffer, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( pService )
	{
		if ( pService->m_reportingState == eRS_StagingRoomHost )
		{
			pService->m_pMatchMaking->QueryKey( key_player, key, index, buffer );
		}
	}
}


// Description:
//	 Called while you are hosting a game for you to report the values of
//	 your team keys
// Arguments:
//	 peer - peer
//	 key - key for which to report information
//	 index - index of the team for which to report info
//	 buffer - fill in the information using this buffer
//	 pParam - user data
void CCryGameSpyLobbyService::PeerQRTeamKeyCallback( PEER peer, int key, int index, qr2_buffer_t buffer, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( pService )
	{
		if ( pService->m_reportingState == eRS_StagingRoomHost )
		{
			pService->m_pMatchMaking->QueryKey( key_team, key, index, buffer );
		}
	}
}


// Description:
//	 Called while you are hosting a game for you to report the list
//	 of game host keys you intend to provide
// Arguments:
//	 peer - peer
//	 type - type of keys being asked for (key_server, key_player, or key_team)
//	 keyBuffer - fill in the keys using this buffer
//	 pParam - user data
void CCryGameSpyLobbyService::PeerQRKeyListCallback( PEER peer, qr2_key_type type, qr2_keybuffer_t keyBuffer, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( pService )
	{
		if ( pService->m_reportingState == eRS_StagingRoomHost )
		{
			pService->m_pMatchMaking->ListKeys( type, keyBuffer );
		}
	}
}


// Description:
//	 Called while you are hosting a game for you to report the number of
//	 players or teams you are hosting
// Arguments:
//	 peer - peer
//	 type - type of count to return (key_player or key_team)
//	 pParam - user data
// Return:
//	 Number of players or teams.
int CCryGameSpyLobbyService::PeerQRCountCallback( PEER peer, qr2_key_type type, void* pParam )
{
	int												result = 0;
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( pService )
	{
		if ( pService->m_reportingState == eRS_StagingRoomHost )
		{
			switch ( type )
			{
			case key_player:

				result = pService->GetGameSpyMatchMaking()->GetCurrentSessionPlayerCount();
				break;
			}
		}
	}

	return result;
}


// Description:
//	 Called when you are hosting a game if there was an error getting your game
//	 host published to the list
// Arguments:
//	 peer - peer
//	 qr2_error_t - type of error
//	 pErrorString - text string containing the error
//	 pParam - user data
void CCryGameSpyLobbyService::PeerQRAddErrorCallback( PEER peer, qr2_error_t error, gsi_char* pErrorString, void* pParam )
{
	CRY_ASSERT_MESSAGE( 0, "Implemement CCryGameSpyLobbyService::PeerQRAddErrorCallback" );
}
#endif // !defined(PURE_CLIENT)

void CCryGameSpyLobbyService::NATNegotiateProgressCallback(NegotiateState state, void* pUserData)
{
	NetLog( "[Lobby] Processing NAT negotiation as server" );
	NetLog( "[Lobby]     Cookie:             %x", ( uint32 )pUserData );

	switch ( state )
	{
	case ns_initack:
		NetLog( "[Lobby]     Request acknowledged by NAT negotiation server" );
		break;
	case ns_connectping:
		NetLog( "[Lobby]     Direct negotiation with partner started" );
		break;
	}
}

void CCryGameSpyLobbyService::NATNegotiateCompletedCallback(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in *pRemoteAddr, void *pUserData)
{
	NetLog( "[Lobby] Finishing NAT negotiation as server" );
	NetLog( "[Lobby]     Cookie:             %x", ( uint32 )pUserData );

	if ( result == nr_success )
	{
		uint32	useIP = pRemoteAddr->sin_addr.s_addr;
		uint16	usePort = pRemoteAddr->sin_port;

		NetLog( "[Lobby]     NAT negotiation succeeded" );
		NetLog( "[Lobby]     Negotiated address: %d.%d.%d.%d:%d",
			( ( uint8* )&useIP )[ 0 ], ( ( uint8* )&useIP )[ 1 ],
			( ( uint8* )&useIP )[ 2 ], ( ( uint8* )&useIP )[ 3 ], ntohs( usePort ) );
	}
	else
	{
		NetLog( "[Lobby]     NAT negotiation failed" ); 
		NetLog( "[Lobby]     Error:              %d", result ); 
	}
}

// Description:
//	 Called while you are hosting a game when a NAT-negotiation cookie is
//	 received from a client that would like to NAT negotiate to connect with you
// Arguments:
//	 peer - peer
//	 cookie - cookie sent from a potential client
//	 pParam - user data
void CCryGameSpyLobbyService::PeerQRNatNegotiateCallback( PEER peer, int cookie, void* pParam )
{
	CCryGameSpyLobbyService* pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if (pService)
	{
		IDatagramSocketPtr pSocket = pService->m_pLobby->GetInternalSocket(pService->m_service);

		if (pSocket)
		{
			NegotiateError	error;

			NetLog( "[Lobby] Starting NAT negotiation as server" );
			NetLog( "[Lobby]     Cookie:             %x", cookie );

			error = NNBeginNegotiationWithSocket(pSocket->GetSysSocket(), cookie, 1, NATNegotiateProgressCallback, NATNegotiateCompletedCallback, ( void* )cookie );

			if ( error == ne_noerror )
			{
				NetLog( "[Lobby]     NAT negotiation started successfully" );
			}
			else
			{
				NetLog( "[Lobby]     NAT negotiation failed to start" );
				NetLog( "[Lobby]     Error:              %d", error ); 
			}
		}
	}
}


// Description:
//	 Called when you are hosting a game to report your publicly visible IP
//	 address to you
// Arguments:
//	 peer - peer
//	 ip - public reporting IP
//	 port - public reporting port
//	 pParam - user data
void CCryGameSpyLobbyService::PeerQRPublicAddressCallback( PEER peer, unsigned int ip, unsigned short port, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( pService )
	{
		pService->m_peerPublicIP = ip;
		pService->m_peerPublicPort = htons( port );
		pService->m_havePublicAddress = true;
	}
}


// Description:
//	 Called when an error occurs while selecting a nick.
// Arguments:
//	 peer - peer
//	 type - type of error
//	 pNick - bad nick
//	 numSuggestedNicks - number of suggested nicks
//	 ppSuggestedNicks - suggested nicks
//	 pParam - user data
void CCryGameSpyLobbyService::PeerNickErrorCallback( PEER peer, int type, const gsi_char* pNick, int numSuggestedNicks, const gsi_char** ppSuggestedNicks, void* pParam )
{
	// Can't happen, we connect the GPConnection first and any nick problems would have been caught at that point.
}


// Description:
//	 Called when a peerConnect completes.
// Arguments:
//	 peer - peer
//	 success - did the peerConnect succeed?
//	 failureReason - reason for failure
//	 pParam - user data
void CCryGameSpyLobbyService::PeerConnectCallback( PEER peer, PEERBool success, int failureReason, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( pService )
	{
		if ( success )
		{
			pService->SetGameSpyObjectState( eGSO_Peer, eCGSOS_Connected, eCLE_Success );
		}
		else
		{
			ECryLobbyError						reason = eCLE_InternalError;

			switch ( failureReason )
			{
			case PEER_DISCONNECTED:
				reason = eCLE_ServerUnreachable;
				break;
			case PEER_NICK_ERROR:
				reason = eCLE_UnhandledNickError;
				break;
			case PEER_LOGIN_FAILED:
				reason = eCLE_InvalidLoginCredentials;
				break;
			}

			pService->SetGameSpyObjectState( eGSO_Peer, eCGSOS_Initialized, reason );
		}
	}
}


// Description:
//	 Called when a peerListGroupRooms completes.
// Arguments:
//	 peer - peer
//	 success - was the peerListGroupRooms successful
//	 groupID - group ID
//	 server - server
//	 pName - name
//	 numWaiting - number of players in room.
//	 maxWaiting - maximum number of players in room.
//	 numGames - number of games in the group
//	 numPlaying - number of players in games in the group
//	 pParam - user data
void CCryGameSpyLobbyService::PeerListGroupRoomsCallback( PEER peer, PEERBool success, int groupID, SBServer server, const gsi_char* pName, int numWaiting, int maxWaiting, int numGames, int numPlaying, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );
	SGroupRoom				room;

	if ( pService )
	{
		if ( groupID )
		{
			room.m_id = groupID;
			room.m_server = server;
			pService->m_groupRooms.push_back( room );
		}
		else
		{
			pService->m_groupRoomRefreshState = eTS_Succeeded;
		}
	}
}


void CCryGameSpyLobbyService::GPErrorCallback( GPConnection* pGPConnection, void* pArg, void* pUserData )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pUserData );
	GPErrorArg*								pErrorArg = static_cast< GPErrorArg* >( pArg );

	if ( pService && pErrorArg )
	{
		if ( pErrorArg->fatal == GP_FATAL )
		{
			ECryLobbyError disconnectReason = eCLE_UserNotSignedIn;

			if ( pErrorArg->errorCode == GP_FORCED_DISCONNECT )
			{
				pService->m_email.clear();
				pService->m_nick.clear();
				pService->m_uniqueNick.clear();
				pService->m_password.clear();
				pService->m_requireNewCredentials = true;
				disconnectReason = eCLE_MultipleSignIn;
			}
			else if(pErrorArg->errorCode == GP_LOGIN_BAD_UNIQUENICK)
			{
				disconnectReason = eCLE_InvalidParam;
			}
			else if(pErrorArg->errorCode == GP_LOGIN_BAD_PASSWORD)
			{
				disconnectReason = eCLE_PasswordIncorrect;
			}

			pService->SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, disconnectReason );
		}
	}
}


void CCryGameSpyLobbyService::StartGameSpyAvailabilityCheck()
{
	int64 startTime = gEnv->pTimer->GetAsyncTime().GetMilliSecondsAsInt64();
	
	GSIStartAvailableCheck( m_title.c_str() );
	SetGameSpyObjectState( eGSO_AvailabilityCheck, eCGSOS_Initializing, eCLE_Success );

	if ((gEnv->pTimer->GetAsyncTime().GetMilliSecondsAsInt64() - startTime) > GAMESPY_STALL_TIMEOUT)
	{
		SetGameSpyObjectState( eGSO_AvailabilityCheck, eCGSOS_NotInitialized, eCLE_ServerUnreachable );
	}
}


void CCryGameSpyLobbyService::WaitForGameSpyAvailabilityCheck()
{
	switch ( GSIAvailableCheckThink() )
	{
	case GSIACAvailable:
		SetGameSpyObjectState( eGSO_AvailabilityCheck, eCGSOS_Initialized, eCLE_Success );
		ptTrackUsage( 0, m_productID, m_gameVersionString, m_distributionID, PTFalse );
		break;
	case GSIACUnavailable:
	case GSIACTemporarilyUnavailable:
		SetGameSpyObjectState( eGSO_AvailabilityCheck, eCGSOS_NotInitialized, eCLE_ObjectNotConnected );
		break;
	}
}


void CCryGameSpyLobbyService::InitializeGPConnection()
{
	ECryLobbyError			ret = eCLE_Success;

	gpInitialize( &m_GPConnection, m_productID, m_namespaceID, m_partnerID );
	gpSetCallback( &m_GPConnection, GP_ERROR, GPErrorCallback, this );

	if (m_pFriends != NULL)
	{
		m_pFriends->InitialiseCallbacks(&m_GPConnection);
	}

	if (m_pStats != NULL)
	{
		m_pStats->StartSake();								// TODO Error handling
	}

	SetGameSpyObjectState( eGSO_GPConnection, eCGSOS_Initialized, eCLE_Success );
}


void CCryGameSpyLobbyService::InitializePeer()
{
	PEERBool				pingRooms[ 3 ] = { PEERFalse, PEERFalse, PEERFalse };
	PEERBool				crossPingRooms[ 3 ] = { PEERFalse, PEERFalse, PEERFalse };

	m_peerCallbacks.disconnected = &PeerDisconnectedCallback;
	m_peerCallbacks.roomMessage = &PeerRoomMessageCallback;
	m_peerCallbacks.roomUTM = &PeerRoomUTMCallback;
	m_peerCallbacks.roomNameChanged = &PeerRoomNameChangedCallback;
	m_peerCallbacks.roomModeChanged = &PeerRoomModeChangedCallback;
	m_peerCallbacks.playerMessage = &PeerPlayerMessageCallback;
	m_peerCallbacks.playerUTM = &PeerPlayerUTMCallback;
	m_peerCallbacks.readyChanged = &PeerReadyChangedCallback;
	m_peerCallbacks.gameStarted = &PeerGameStartedCallback;
	m_peerCallbacks.playerJoined = &PeerPlayerJoinedCallback;
	m_peerCallbacks.playerLeft = &PeerPlayerLeftCallback;
	m_peerCallbacks.kicked = &PeerKickedCallback;
	m_peerCallbacks.newPlayerList = &PeerNewPlayerListCallback;
	m_peerCallbacks.playerChangedNick = &PeerPlayerChangedNickCallback;
	m_peerCallbacks.playerInfo = &PeerPlayerInfoCallback;
	m_peerCallbacks.playerFlagsChanged = &PeerPlayerFlagsChangedCallback;
	m_peerCallbacks.ping = &PeerPingCallback;
	m_peerCallbacks.crossPing = &PeerCrossPingCallback;
	m_peerCallbacks.globalKeyChanged = &PeerGlobalKeyChangedCallback;
	m_peerCallbacks.roomKeyChanged = &PeerRoomKeyChangedCallback;
#if !defined(PURE_CLIENT)
	m_peerCallbacks.qrServerKey = &PeerQRServerKeyCallback;
	m_peerCallbacks.qrPlayerKey = &PeerQRPlayerKeyCallback;
	m_peerCallbacks.qrTeamKey = &PeerQRTeamKeyCallback;
	m_peerCallbacks.qrKeyList = &PeerQRKeyListCallback;
	m_peerCallbacks.qrCount = &PeerQRCountCallback;
	m_peerCallbacks.qrAddError = &PeerQRAddErrorCallback;
#else
	m_peerCallbacks.qrServerKey = NULL;
	m_peerCallbacks.qrPlayerKey = NULL;
	m_peerCallbacks.qrTeamKey = NULL;
	m_peerCallbacks.qrKeyList = NULL;
	m_peerCallbacks.qrCount = NULL;
	m_peerCallbacks.qrAddError = NULL;
#endif // !defined(PURE_CLIENT)
	m_peerCallbacks.qrNatNegotiateCallback = &PeerQRNatNegotiateCallback;
	m_peerCallbacks.qrPublicAddressCallback = &PeerQRPublicAddressCallback;
	m_peerCallbacks.param = this;
	m_peer = peerInitialize( &m_peerCallbacks );
	peerSetTitle( m_peer, m_title.c_str(), m_secretKey.c_str(), m_title.c_str(), m_secretKey.c_str(), m_gameVersion, k_defaultMaxUpdates, PEERTrue, pingRooms, crossPingRooms );

	SetGameSpyObjectState( eGSO_Peer, eCGSOS_Initialized, eCLE_Success );
}


void CCryGameSpyLobbyService::DispatchNickRejected( uint32 numNicks, char** ppNicks, bool disconnect, ECryLobbyError reason )
{
	LOBBY_AUTO_LOCK;

	SCryLobbyOnlineNameRejectedData	onlineNameRejectedData;
	UCryLobbyEventData							eventData;

	onlineNameRejectedData.m_user = 0;
	onlineNameRejectedData.m_numSuggestedNames = numNicks;
	onlineNameRejectedData.m_ppSuggestedNames = ppNicks;
	eventData.pSuggestedNames = &onlineNameRejectedData;
	m_pLobby->DispatchEvent( eCLSE_OnlineNameRejected, eventData );

	for ( uint32 i = 0; i < numNicks; ++i )
	{
		SAFE_DELETE_ARRAY( ppNicks[ i ] );
	}

	SAFE_DELETE_ARRAY( ppNicks );

	if ( disconnect )
	{
		FROM_GAME_TO_LOBBY( &CCryGameSpyLobbyService::GameSpyDisconnect, this, reason );
	}
}


void CCryGameSpyLobbyService::SetGameSpyObjectState( EGameSpyObject object, ECryGameSpyObjectState state, ECryLobbyError reason )
{
	bool oldMakingAvailable = false;
	bool newMakingAvailable = false;
	bool oldAvailable = false;
	bool newAvailable = false;
	EOnlineState	oldGPConnectionOnlineState = eOS_SignedOut;
	EOnlineState	newGPConnectionOnlineState = eOS_SignedOut;
	EOnlineState	oldPeerOnlineState = eOS_SignedOut;
	EOnlineState	newPeerOnlineState = eOS_SignedOut;
	EOnlineState	oldOnlineState = eOS_SignedOut;
	EOnlineState	newOnlineState = eOS_SignedOut;
	bool stateChanged = m_states[ object ] != state;

	if ( !( ( object >= eGSO_Core ) && ( object < eGSO_Num ) ) )
	{
		NetLogAlways( "[GameSpy] CCryGameSpyLobbyService::SetGameSpyObjectState: Invalid EGameSpyObject %d", object );
		return;
	}

	switch ( m_states[ eGSO_GPConnection ] )
	{
	case eCGSOS_NotInitialized:
		// FALL THROUGH
	case eCGSOS_Initializing:
		// FALL THROUGH
	case eCGSOS_Initialized:
		// FALL THROUGH
	case eCGSOS_Connecting:
		oldGPConnectionOnlineState = eOS_SignedOut;
		break;
	case eCGSOS_Connected:
		oldGPConnectionOnlineState = eOS_SignedIn;
		break;
	}

	switch ( m_states[ eGSO_Peer ] )
	{
	case eCGSOS_NotInitialized:
		// FALL THROUGH
	case eCGSOS_Initializing:
		// FALL THROUGH
	case eCGSOS_Initialized:
		// FALL THROUGH
	case eCGSOS_Connecting:
		oldPeerOnlineState = eOS_SignedOut;
		break;
	case eCGSOS_Connected:
		oldPeerOnlineState = eOS_SignedIn;
		break;
	}

	if ( ( oldGPConnectionOnlineState == eOS_SignedIn ) && ( oldPeerOnlineState == eOS_SignedIn ) )
	{
		oldOnlineState = eOS_SignedIn;
	}
	else
	{
		oldOnlineState = eOS_SignedOut;
	}

	switch ( object )
	{
	case eGSO_Core:
		// FALL THROUGH
	case eGSO_AvailabilityCheck:
		// FALL THROUGH
	case eGSO_AuthService:
		// FALL THROUGH
	case eGSO_CDKey:
		oldMakingAvailable = ( m_states[ object ] == eCGSOS_Initializing );
		newMakingAvailable = ( state == eCGSOS_Initializing );
		oldAvailable = ( m_states[ object ] == eCGSOS_Initialized );
		newAvailable = ( state == eCGSOS_Initialized );
		break;
	case eGSO_GPConnection:
		oldMakingAvailable = ( m_states[ object ] == eCGSOS_Connecting );
		newMakingAvailable = ( state == eCGSOS_Connecting );
		oldAvailable = ( m_states[ object ] == eCGSOS_Connected );
		newAvailable = ( state == eCGSOS_Connected );
		break;
	case eGSO_Peer:
		oldMakingAvailable = ( m_states[ object ] == eCGSOS_Connecting );
		newMakingAvailable = ( state == eCGSOS_Connecting );
		oldAvailable = ( m_states[ object ] == eCGSOS_Connected );
		newAvailable = ( state == eCGSOS_Connected );
		break;
	}

	m_states[ object ] = state;

	switch ( m_states[ eGSO_GPConnection ] )
	{
	case eCGSOS_NotInitialized:
		// FALL THROUGH
	case eCGSOS_Initializing:
		// FALL THROUGH
	case eCGSOS_Initialized:
		// FALL THROUGH
	case eCGSOS_Connecting:
		newGPConnectionOnlineState = eOS_SignedOut;
		break;
	case eCGSOS_Connected:
		newGPConnectionOnlineState = eOS_SignedIn;
		break;
	}

	if ( ( oldGPConnectionOnlineState == eOS_SignedOut ) && ( newGPConnectionOnlineState == eOS_SignedIn ) )
	{
		m_targetGPStatus = GP_ONLINE;
		m_targetGPStatusString = "Online";
		m_targetGPLocationString = "";
		m_GPStatusDirty = true;
	}
	else
	{
		if ( ( oldGPConnectionOnlineState == eOS_SignedIn ) && ( newGPConnectionOnlineState == eOS_SignedOut ) )
		{
			m_targetGPStatus = GP_OFFLINE;
			m_targetGPStatusString = "Offline";
			m_targetGPLocationString = "";
			m_reportedGPStatus = GP_OFFLINE;
			m_reportedGPStatusString = "Offline";
			m_reportedGPLocationString = "";
			m_GPStatusDirty = false;
		}
	}

	switch ( m_states[ eGSO_Peer ] )
	{
	case eCGSOS_NotInitialized:
		// FALL THROUGH
	case eCGSOS_Initializing:
		// FALL THROUGH
	case eCGSOS_Initialized:
		// FALL THROUGH
	case eCGSOS_Connecting:
		newPeerOnlineState = eOS_SignedOut;
		break;
	case eCGSOS_Connected:
		newPeerOnlineState = eOS_SignedIn;
		break;
	}

	if ( ( newGPConnectionOnlineState == eOS_SignedIn ) && ( newPeerOnlineState == eOS_SignedIn ) )
	{
		newOnlineState = eOS_SignedIn;
	}
	else
	{
		newOnlineState = eOS_SignedOut;
	}

	if ( newAvailable && !oldAvailable )
	{
		OnGameSpyObjectAvailable( ( EGameSpyObjectBit )BIT( object ) );
	}
	else if ( oldAvailable && !newAvailable )
	{
		OnGameSpyObjectUnavailable( ( EGameSpyObjectBit )BIT( object ) );
	}
	else if ( oldMakingAvailable && ( !newMakingAvailable ) )
	{
		// We were trying to make this object available, but now we're not ...
		if ( !newAvailable )
		{
			// ... and it's not available now, so we tried to make it available and failed.
			OnGameSpyObjectCantMakeAvailable( ( EGameSpyObjectBit )BIT( object ) );

			for ( CryLobbyServiceTaskID lsTaskID = 0; lsTaskID < MAX_LOBBY_TASKS; ++lsTaskID )
			{
				STask*		pTask = GetTask( lsTaskID );

				if ( pTask->used && pTask->running )
				{
					switch ( pTask->startedTask )
					{
					case eT_GetUserPrivileges:
						// FALL THROUGH
					case eT_GetUserCredentials:
						if ( object == eGSO_GPConnection )
						{
							UpdateTaskError( lsTaskID, reason );
							StopTaskRunning( lsTaskID );
						}
						break;
					}
				}
			}
		}
	}

	if ( newOnlineState != oldOnlineState )
	{
		TO_GAME_FROM_LOBBY( &CCryGameSpyLobbyService::DispatchOnlineStatus, this, eCLSE_OnlineState, newOnlineState, reason );

		if( newOnlineState == eOS_SignedOut )
		{
			if ( m_usingCDKey )
			{
				gcd_disconnect_user( m_gameID, GAMESPY_CD_KEY_LOCALID_SERVER );
				m_usingCDKey = false;
			}

			ClearSignedInDetails();
		}
	}
	else
	{
		if ( ( newOnlineState == eOS_SignedOut ) && ( reason != eCLE_Success ) && stateChanged )
		{
			TO_GAME_FROM_LOBBY( &CCryGameSpyLobbyService::DispatchOnlineStatus, this, eCLSE_LoginFailed, newOnlineState, reason );
		}
	}
}


void CCryGameSpyLobbyService::OnGameSpyObjectAvailable( EGameSpyObjectBit object )
{
	if ( m_pMatchMaking )
	{
		m_pMatchMaking->OnGameSpyObjectAvailable( object );
	}

	if ( m_pStats )
	{
		m_pStats->OnGameSpyObjectAvailable( object );
	}

	if ( m_pFriends )
	{
		m_pFriends->OnGameSpyObjectAvailable( object );
	}

	if ( m_pFriendsManagement )
	{
		m_pFriendsManagement->OnGameSpyObjectAvailable( object );
	}

	if ( m_pDLCStore )
	{
		m_pDLCStore->OnGameSpyObjectAvailable( object );
	}
}


void CCryGameSpyLobbyService::OnGameSpyObjectUnavailable( EGameSpyObjectBit object )
{
	if ( m_pMatchMaking )
	{
		m_pMatchMaking->OnGameSpyObjectUnavailable( object );
	}

	if ( m_pStats )
	{
		m_pStats->OnGameSpyObjectUnavailable( object );
	}

	if ( m_pFriends )
	{
		m_pFriends->OnGameSpyObjectUnavailable( object );
	}

	if ( m_pFriendsManagement )
	{
		m_pFriendsManagement->OnGameSpyObjectUnavailable( object );
	}

	if ( m_pDLCStore )
	{
		m_pDLCStore->OnGameSpyObjectUnavailable( object );
	}
}


void CCryGameSpyLobbyService::OnGameSpyObjectCantMakeAvailable( EGameSpyObjectBit object )
{
	uint32	objects = object;

	if ( objects & eGSOB_Core )
	{
		objects |= eGSOB_AvailabilityCheck;
		m_neededGameSpyObjects &= ~eGSOB_Core;
	}

	if ( objects & eGSOB_AvailabilityCheck )
	{
		objects |= eGSOB_GPConnection | eGSOB_Peer | eGSOB_CDKey;
		m_neededGameSpyObjects &= ~eGSOB_AvailabilityCheck;
	}

	if ( objects & eGSOB_GPConnection )
	{
		objects |= eGSOB_AuthService;
		m_neededGameSpyObjects &= ~eGSOB_GPConnection;
	}

	if ( objects & eGSOB_Peer )
	{
		m_neededGameSpyObjects &= ~eGSOB_Peer;
	}

	if ( objects & eGSOB_CDKey )
	{
		m_neededGameSpyObjects &= ~eGSOB_CDKey;
	}

	if ( objects & eGSOB_AuthService )
	{
		m_neededGameSpyObjects &= ~eGSOB_AuthService;
	}

	if ( m_pMatchMaking )
	{
		m_pMatchMaking->OnGameSpyObjectCantMakeAvailable( ( EGameSpyObjectBit )objects );
	}

	if ( m_pStats )
	{
		m_pStats->OnGameSpyObjectCantMakeAvailable( ( EGameSpyObjectBit )objects );
	}

	if ( m_pFriends )
	{
		m_pFriends->OnGameSpyObjectCantMakeAvailable( ( EGameSpyObjectBit )objects );
	}

	if ( m_pFriendsManagement )
	{
		m_pFriendsManagement->OnGameSpyObjectCantMakeAvailable( ( EGameSpyObjectBit )objects );
	}

	if ( m_pDLCStore )
	{
		m_pDLCStore->OnGameSpyObjectCantMakeAvailable( ( EGameSpyObjectBit )objects );
	}
}


uint32 CCryGameSpyLobbyService::GetAvailableGameSpyObjects()
{
	uint32 result = 0;

	if ( m_states[ eGSO_Core ] == eCGSOS_Initialized )
	{
		result |= eGSOB_Core;
	}

	if ( m_states[ eGSO_AvailabilityCheck ] == eCGSOS_Initialized )
	{
		result |= eGSOB_AvailabilityCheck;
	}

	if ( m_states[ eGSO_GPConnection ] == eCGSOS_Connected )
	{
		result |= eGSOB_GPConnection;
	}

	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		result |= eGSOB_Peer;
	}

	if ( m_states[ eGSO_AuthService ] == eCGSOS_Initialized )
	{
		result |= eGSOB_AuthService;
	}

	if ( m_states[ eGSO_CDKey ] == eCGSOS_Initialized )
	{
		result |= eGSOB_CDKey;
	}

	return result;
}

void CCryGameSpyLobbyService::GetSocketPorts(uint16& connectPort,uint16& listenPort)
{
	connectPort = CNetCVars::Get().lobbyGameSpyOnlinePort;
	listenPort = CNetCVars::Get().lobbyGameSpyOnlinePort;
}


void CCryGameSpyLobbyService::TickRoomOps()
{
	GetNextRoomOp();

	if ( m_pCurrentRoomOp )
	{
		switch ( m_pCurrentRoomOp->m_state )
		{
		case eTS_NotStarted:
			StartRoomOp();
			break;
		case eTS_CannotStart:
			CancelRoomOp();
			// FALL THROUGH
		case eTS_Succeeded:
		case eTS_Failed:
			m_pCurrentRoomOp = NULL;
			break;
		}
	}
}


void CCryGameSpyLobbyService::GetNextRoomOp()
{
	if ( !m_pCurrentRoomOp )
	{
		if ( !m_roomOpQueue.empty() )
		{
			SRoomOpPtr	pFront = m_roomOpQueue.front();

			if ( ( ( pFront->m_opType == eROT_Create ) || ( pFront->m_opType == eROT_Join ) ) && ( m_roomJoinState[ pFront->m_roomType ] != eRJS_NotJoined ) )
			{
				SRoomOp*		pNewCurrentRoomOp = new SRoomOp();

				pNewCurrentRoomOp->m_opType = eROT_Leave;
				pNewCurrentRoomOp->m_roomType = pFront->m_roomType;
				pNewCurrentRoomOp->m_state = eTS_NotStarted;
				m_pCurrentRoomOp = pNewCurrentRoomOp;
			}
			else
			{
				m_pCurrentRoomOp = pFront;
				m_roomOpQueue.pop();
			}
		}
	}
}


void CCryGameSpyLobbyService::StartRoomOp()
{
	switch ( m_pCurrentRoomOp->m_opType )
	{
	case eROT_Create:
		StartCreateRoom();
		break;
	case eROT_Join:
		StartJoinRoom();
		break;
	case eROT_Leave:
		StartLeaveRoom();
		break;
	}
}


void CCryGameSpyLobbyService::CancelRoomOp()
{
	switch ( m_pCurrentRoomOp->m_opType )
	{
	case eROT_Create:
	case eROT_Join:

		if ( m_pCurrentRoomOp->m_cb )
		{
			static_cast< peerJoinRoomCallback >( m_pCurrentRoomOp->m_cb )( m_peer, PEERFalse, PEERJoinFailed, m_pCurrentRoomOp->m_roomType, m_pCurrentRoomOp->m_pParam );
		}
		break;
	}
}


void CCryGameSpyLobbyService::StartCreateRoom()
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		if ( m_pCurrentRoomOp->m_roomType == StagingRoom )
		{
			SCreateStagingRoomOp*			pRoomOp = static_cast< SCreateStagingRoomOp* >( m_pCurrentRoomOp.get() );

			IDatagramSocketPtr				socket = m_pLobby->GetInternalSocket(m_service);

			if (socket)
			{
				SOCKET										sysSocket = socket->GetSysSocket();
				sockaddr									addr;
				int												addrLen = sizeof ( addr );
				uint16										port;

				if ( m_reportingState == eRS_Server )
				{
					peerStopGame( m_peer );
				}

				getsockname( sysSocket, &addr, &addrLen );
				port = ntohs( ( ( sockaddr_in* )&addr )->sin_port );
				peerCreateStagingRoomWithSocket( m_peer, pRoomOp->m_pName, pRoomOp->m_maxPlayers, pRoomOp->m_password, sysSocket, port, PeerJoinRoomCallback, this, PEERFalse );
				m_reportingState = eRS_StagingRoomHost;
				m_pCurrentRoomOp->m_state = eTS_InProgress;
				m_roomJoinState[ StagingRoom ] = eRJS_JoinInProgress;
			}
			else
			{
				m_pCurrentRoomOp->m_state = eTS_CannotStart;
			}
		}
		else
		{
			m_pCurrentRoomOp->m_state = eTS_CannotStart;
		}
	}
	else
	{
		m_pCurrentRoomOp->m_state = eTS_CannotStart;
		NetLog( "[GameSpy] Cannot start peerCreateStagingRoomWithSocket with peer not in connected state" );
	}
}


void CCryGameSpyLobbyService::StartJoinRoom()
{
	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		switch ( m_pCurrentRoomOp->m_roomType )
		{
		case TitleRoom:

			peerJoinTitleRoom( m_peer, 0, PeerJoinRoomCallback, this, PEERFalse );
			m_pCurrentRoomOp->m_state = eTS_InProgress;
			m_roomJoinState[ TitleRoom ] = eRJS_JoinInProgress;
			break;

		case GroupRoom:

			{
				SJoinGroupRoomOp*	pRoomOp = static_cast< SJoinGroupRoomOp* >( m_pCurrentRoomOp.get() );

				peerJoinGroupRoom( m_peer, pRoomOp->m_groupID, PeerJoinRoomCallback, this, PEERFalse );
				m_pCurrentRoomOp->m_state = eTS_InProgress;
				m_roomJoinState[ GroupRoom ] = eRJS_JoinInProgress;
			}

			break;

		case StagingRoom:

			{
				SJoinStagingRoomOp*	pRoomOp = static_cast< SJoinStagingRoomOp* >( m_pCurrentRoomOp.get() );

				peerJoinStagingRoom( m_peer, pRoomOp->m_server, pRoomOp->m_password, PeerJoinRoomCallback, this, PEERFalse );
				m_pCurrentRoomOp->m_state = eTS_InProgress;
				m_roomJoinState[ GroupRoom ] = eRJS_JoinInProgress;
			}

			break;
		}

		m_pCurrentRoomOp->m_state = eTS_InProgress;
		m_roomJoinState[ m_pCurrentRoomOp->m_roomType ] = eRJS_JoinInProgress;
	}
	else
	{
		m_pCurrentRoomOp->m_state = eTS_CannotStart;
		NetLog( "[GameSpy] Cannot start peerJoin*Room with peer not in connected state" );
	}
}


void CCryGameSpyLobbyService::StartLeaveRoom()
{
	peerLeaveRoom( m_peer, m_pCurrentRoomOp->m_roomType, NULL );
	m_pCurrentRoomOp->m_state = eTS_Succeeded;
	m_roomJoinState[ m_pCurrentRoomOp->m_roomType ] = eRJS_NotJoined;

	if ( m_pCurrentRoomOp->m_cb )
	{
		static_cast< peerLeaveRoomCallback >( m_pCurrentRoomOp->m_cb )( m_pCurrentRoomOp->m_pParam );
	}
}


void CCryGameSpyLobbyService::PeerJoinRoomCallback( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void* pParam )
{
	CCryGameSpyLobbyService*	pService = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( success )
	{
		pService->m_pCurrentRoomOp->m_state = eTS_Succeeded;
		pService->m_roomJoinState[ pService->m_pCurrentRoomOp->m_roomType ] = eRJS_Joined;
	}
	else
	{
		pService->m_pCurrentRoomOp->m_state = eTS_Failed;
		pService->m_roomJoinState[ pService->m_pCurrentRoomOp->m_roomType ] = eRJS_NotJoined;
	}

	if ( pService->m_pCurrentRoomOp->m_cb )
	{
		static_cast< peerJoinRoomCallback >( pService->m_pCurrentRoomOp->m_cb )( peer, success, result, roomType, pService->m_pCurrentRoomOp->m_pParam );
	}
}

void CCryGameSpyLobbyService::StartTaskRunning(CryLobbyServiceTaskID lsTaskID)
{
	LOBBY_AUTO_LOCK;

	STask* pTask = GetTask(lsTaskID);

	if (pTask->used)
	{
		pTask->running = true;

		switch (pTask->startedTask)
		{
		case eT_GetUserPrivileges:
			StartGetUserPrivileges(lsTaskID);		
			break;
		case eT_UserSignOut:
			StartUserSignOut( lsTaskID );		
			break;
		case eT_GetUserCredentials:
			StartGetUserCredentials( lsTaskID );		
			break;
		case eT_CheckProfanity:
			StartCheckProfanity( lsTaskID );		
			break;
		}
	}
}

void CCryGameSpyLobbyService::StopTaskRunning(CryLobbyServiceTaskID lsTaskID)
{
	STask* pTask = GetTask(lsTaskID);

	if (pTask->used)
	{
		pTask->running = false;

		TO_GAME_FROM_LOBBY(&CCryGameSpyLobbyService::EndTask, this, lsTaskID);
	}
}

void CCryGameSpyLobbyService::EndTask(CryLobbyServiceTaskID lsTaskID)
{
	LOBBY_AUTO_LOCK;

	STask* pTask = GetTask(lsTaskID);

	if (pTask->used)
	{
		if (pTask->pCB)
		{
			switch (pTask->startedTask)
			{
			case eT_GetUserPrivileges:
				{
					uint32 result = 0;
					if (pTask->error != eCLE_Success)
					{
						result |= CLPF_BlockMultiplayerSessons;
					}
	
					((CryLobbyPrivilegeCallback)pTask->pCB)(pTask->lTaskID, pTask->error, result, pTask->pCBArg);
				}
				break;
			case eT_UserSignOut:
				( ( CryLobbyOnlineStateCallback )pTask->pCB )( pTask->lTaskID, pTask->error, pTask->pCBArg );
				break;
			case eT_GetUserCredentials:
				EndGetUserCredentials( lsTaskID );
				break;
			case eT_CheckProfanity:
				EndCheckProfanity( lsTaskID );
				break;
			}
		}

		if (pTask->error != eCLE_Success)
		{
			NetLog("[Lobby] Lobby Service EndTask %d Result %d", pTask->startedTask, pTask->error);
		}

		FreeTask(lsTaskID);
	}
}

ECryLobbyError CCryGameSpyLobbyService::GetUserPrivileges(uint32 user, CryLobbyTaskID* pTaskID, CryLobbyPrivilegeCallback pCB, void* pCBArg)
{
	ECryLobbyError error;
	CryLobbyServiceTaskID tid;

	LOBBY_AUTO_LOCK;

	error = StartTask(eT_GetUserPrivileges, user, false, &tid, pTaskID, (void*)pCB, pCBArg);

	if (error == eCLE_Success)
	{
		STask*					pTask = &m_tasks[ tid ];

		pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_STATE_PARAM ] = GAMESPY_GET_USER_PRIVILEGES_CD_KEY_NOT_STARTED;
		pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_ERROR_PARAM ] = eCLE_Success;
		FROM_GAME_TO_LOBBY(&CCryGameSpyLobbyService::StartTaskRunning, this, tid);
	}

	return error;
}

ECryLobbyError CCryGameSpyLobbyService::GetSystemTime(uint32 user, SCrySystemTime* pSystemTime)
{
	memset(pSystemTime, 0, sizeof(SCrySystemTime));

	time_t ltime;
	time(&ltime);
	tm* pToday = localtime(&ltime);

	pSystemTime->m_Year = pToday->tm_year + 1900;
	pSystemTime->m_Month = static_cast<uint8>(pToday->tm_mon + 1);
	pSystemTime->m_Day = static_cast<uint8>(pToday->tm_mday);
	pSystemTime->m_Hour = static_cast<uint8>(pToday->tm_hour);
	pSystemTime->m_Minute = static_cast<uint8>(pToday->tm_min);
	pSystemTime->m_Second = static_cast<uint8>(pToday->tm_sec);

	NetLog("**WARNING** CryGameSpyLobbyService::GetSystemTime() is not a trusted time source");
	return eCLE_Success;
}

void CCryGameSpyLobbyService::StartGetUserPrivileges(CryLobbyServiceTaskID lsTaskID)
{
	if (m_pLobby->GetInternalSocket(m_service))
	{
		m_connectionWaitTime = g_time.GetMilliSecondsAsInt64() + CNetCVars::Get().connectionWaitTimeOut;
		SetNeedGPConnection(true);
		SetNeedPeer(true);
		SetNeedCDKey(true);
	}
	else
	{
		UpdateTaskError(lsTaskID, eCLE_InternalError);
		StopTaskRunning(lsTaskID);
	}
}

void CCryGameSpyLobbyService::TickGetUserPrivileges(CryLobbyServiceTaskID lsTaskID)
{
	if ( ( m_states[ eGSO_GPConnection ] == eCGSOS_Connected ) && ( m_states[ eGSO_Peer ] == eCGSOS_Connected ) && ( m_states[ eGSO_CDKey ] == eCGSOS_Initialized ) )		// I guess i need a timeout
	{
		STask*				pTask = &m_tasks[ lsTaskID ];

#ifndef _RELEASE
		if ( CNetCVars::Get().gamespyCheckCDKey )
#endif
		{
			if ( m_usingCDKey )
			{
				pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_STATE_PARAM ] = GAMESPY_GET_USER_PRIVILEGES_CD_KEY_SUCCEEDED;
			}

			switch ( pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_STATE_PARAM ] )
			{
			case GAMESPY_GET_USER_PRIVILEGES_CD_KEY_NOT_STARTED:
				if ( m_havePublicAddress )
				{
					char					challenge[ GAMESPY_CD_KEY_CHALLENGE_SIZE ];
					char					response[ GAMESPY_CD_KEY_RESPONSE_SIZE ];

					SConfigurationParams	neededInfo[ 1 ] =
					{
						{ CLCC_GAMESPY_CDKEY,						{ NULL } }
					};

					m_pLobby->GetConfigurationInformation( neededInfo, ARRAY_COUNT( neededInfo ) );
					m_pMatchMaking->CreateChallengeString( challenge );
					gcd_compute_response( static_cast< char* >( neededInfo[ 0 ].m_pData ), challenge, response, CDResponseMethod_NEWAUTH );
					gcd_authenticate_user( m_gameID, GAMESPY_CD_KEY_LOCALID_SERVER, m_peerPublicIP, challenge, response, LocalCDKeyAuthCallback, LocalCDKeyReauthCallback, this );
					pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_STATE_PARAM ] = GAMESPY_GET_USER_PRIVILEGES_CD_KEY_IN_PROGRESS;
				}
				break;
			case GAMESPY_GET_USER_PRIVILEGES_CD_KEY_FAILED:
				UpdateTaskError( lsTaskID, ( ECryLobbyError )pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_ERROR_PARAM ] );
				// FALL THROUGH
			case GAMESPY_GET_USER_PRIVILEGES_CD_KEY_SUCCEEDED:
				StopTaskRunning( lsTaskID );
				break;
			}
		}
#ifndef _RELEASE
		else
		{
			StopTaskRunning( lsTaskID );
		}
#endif
	}
	else
	{
		if (m_connectionWaitTime <= g_time.GetMilliSecondsAsInt64())
		{
			UpdateTaskError(lsTaskID, eCLE_InternalError);
			StopTaskRunning(lsTaskID);
		}
	}
}

bool CCryGameSpyLobbyService::PeerIsInCorrectStagingRoom( SCryGameSpyRemoteConnectionID* id )
{
	bool			result = true;
	SBServer	server = NULL;

	if ( m_states[ eGSO_Peer ] == eCGSOS_Connected )
	{
		server = peerGetHostServer( m_peer );
	}

	if ( id )
	{
		if ( ( !server ) || ( ( SBServerGetPublicInetAddress( server ) != id->m_publicIP ) || ( htons( SBServerGetPublicQueryPort( server ) ) != id->m_publicPort ) ) )
		{
			result = false;
		}
	}
	else
	{
		if ( server )
		{
			result = false;
		}
	}

	return result;
}

void CCryGameSpyLobbyService::AuthServiceStartLogin()
{
	ECryLobbyLoginGUIState	result = GetLoginCredentials();

	if ( result != eCLLGS_NotFinished )
	{
		SetGameSpyObjectState( eGSO_AuthService, eCGSOS_Initializing, eCLE_Success );
	}

	if ( m_requireNewCredentials )
	{
		SetGameSpyObjectState( eGSO_AuthService, eCGSOS_NotInitialized, eCLE_UserNotSignedIn );
	}
	else
	{
		switch ( result )
		{
		case eCLLGS_NotFinished:
			break;
		case eCLLGS_Cancelled:
			SetGameSpyObjectState( eGSO_AuthService, eCGSOS_NotInitialized, eCLE_InvalidLoginCredentials );
			break;
		case eCLLGS_NewAccount:
			// FALL THROUGH
		case eCLLGS_NotImplemented:
			// FALL THROUGH
		case eCLLGS_ExistingAccount:
			if ( wsLoginUnique( m_partnerID, m_namespaceID, m_uniqueNick, m_password, NULL, AuthServiceWSLoginCallback, this ) != WSLogin_Success )
			{
				SetGameSpyObjectState( eGSO_AuthService, eCGSOS_NotInitialized, eCLE_InvalidRequest );
			}
			break;
		}
	}
}

void CCryGameSpyLobbyService::AuthServiceWSLoginCallback( GHTTPResult httpResult, WSLoginResponse* pResponse, void* pUserData )
{
	CCryGameSpyLobbyService*	pLobbyService = static_cast< CCryGameSpyLobbyService* >( pUserData );

	if ( ( httpResult == GHTTPSuccess ) && ( pResponse->mLoginResult == WSLogin_Success ) )
	{
		memcpy( &pLobbyService->m_authServiceCertificate, &pResponse->mCertificate, sizeof ( pResponse->mCertificate ) );
		memcpy( &pLobbyService->m_authServicePrivateData, &pResponse->mPrivateData, sizeof ( pResponse->mPrivateData ) );
		pLobbyService->SetGameSpyObjectState( eGSO_AuthService, eCGSOS_Initialized, eCLE_Success );
	}
	else
	{
		switch ( pResponse->mResponseCode )
		{
		case WSLogin_UserNotFound:
			// FALL THROUGH
		case WSLogin_InvalidPassword:
			// FALL THROUGH
		case WSLogin_InvalidProfile:
			// FALL THROUGH
		case WSLogin_UniqueNickExpired:
			pLobbyService->SetGameSpyObjectState( eGSO_AuthService, eCGSOS_NotInitialized, eCLE_InvalidLoginCredentials );
			break;
		default:
			pLobbyService->SetGameSpyObjectState( eGSO_AuthService, eCGSOS_NotInitialized, eCLE_InternalError);
			break;
		}
	}
}

void CCryGameSpyLobbyService::AuthServiceCheckCertificate()
{
	if ( !wsLoginCertIsValid( &m_authServiceCertificate ) )
	{
		SetGameSpyObjectState( eGSO_AuthService, eCGSOS_NotInitialized, eCLE_ObjectNotConnected );
	}
}

void CCryGameSpyLobbyService::AuthServiceInvalidateCertificate()
{
	SetGameSpyObjectState( eGSO_AuthService, eCGSOS_NotInitialized, eCLE_ObjectNotConnected );
}

void CCryGameSpyLobbyService::SetNeedAvailabilityCheck( bool needAvailabilityCheck )
{
	SetNeedGameSpyObject( eGSOB_AvailabilityCheck, needAvailabilityCheck );
}

void CCryGameSpyLobbyService::SetNeedAuthService( bool needAuthService )
{
	SetNeedGameSpyObject( eGSOB_AuthService, needAuthService );
}

GSLoginCertificate* CCryGameSpyLobbyService::AuthServiceGetCertificate()
{
	GSLoginCertificate*	pResult = NULL;

	if ( m_states[ eGSO_AuthService ] == eCGSOS_Initialized )
	{
		pResult = &m_authServiceCertificate;
	}

	return pResult;
}

GSLoginPrivateData* CCryGameSpyLobbyService::AuthServiceGetPrivateData()
{
	GSLoginPrivateData*	pResult = NULL;

	if ( m_states[ eGSO_AuthService ] == eCGSOS_Initialized )
	{
		pResult = &m_authServicePrivateData;
	}

	return pResult;
}

void CCryGameSpyLobbyService::GPSetStatus( GPEnum status )
{
	m_targetGPStatus = status;

	if ( m_targetGPStatus != m_reportedGPStatus )
	{
		m_GPStatusDirty = true;
	}
}

void CCryGameSpyLobbyService::GPSetStatusString( const char* pStatusString )
{
	m_targetGPStatusString = pStatusString;

	if ( m_targetGPStatusString != m_reportedGPStatusString )
	{
		m_GPStatusDirty = true;
	}
}

void CCryGameSpyLobbyService::GPSetLocationString( const char* pLocationString )
{
	m_targetGPLocationString = pLocationString;

	if ( m_targetGPLocationString != m_reportedGPLocationString )
	{
		m_GPStatusDirty = true;
	}
}

void CCryGameSpyLobbyService::NATDetectionCallback( gsi_bool success, NAT nat )
{
	if ( success )
	{
		switch ( nat.natType )
		{
		case no_nat:
			// FALL THROUGH
		case full_cone:
			s_NATType = eNT_Open;
			break;
		case restricted_cone:
			// FALL THROUGH
		case port_restricted_cone:
			s_NATType = eNT_Moderate;
			break;
		case firewall_only:
			switch ( nat.promiscuity )
			{
			case promiscuous:
				s_NATType = eNT_Open;
				break;
			default:
				s_NATType = eNT_Moderate;
				break;
			}
			break;
		case symmetric:
			s_NATType = eNT_Strict;
			break;
		}

		s_NATDetectionState = eNDS_Succeeded;
	}
	else
	{
		s_NATDetectionState = eNDS_Failed;
	}
}

void CCryGameSpyLobbyService::DispatchNATType()
{
	if ( m_pLobby )
	{
		UCryLobbyEventData		data;
		SCryLobbyNatTypeData	natTypeData;

		natTypeData.m_curState = s_NATType;
		data.pNatTypeData = &natTypeData;
		m_pLobby->DispatchEvent( eCLSE_NatType, data );
	}
}

void CCryGameSpyLobbyService::ClearSignedInDetails()
{
	if( m_pDLCStore )
	{
		m_pDLCStore->Terminate();
	}

	AuthServiceInvalidateCertificate();
}

ECryLobbyError CCryGameSpyLobbyService::UserSignOut( uint32 user, CryLobbyTaskID* pTaskID, CryLobbyOnlineStateCallback pCb, void* pCbArg )
{
	ECryLobbyError				error;
	CryLobbyServiceTaskID	tid;

	LOBBY_AUTO_LOCK;

	error = StartTask( eT_UserSignOut, user, false, &tid, pTaskID, ( void* )pCb, pCbArg );

	if ( error == eCLE_Success )
	{
		STask*								pTask = &m_tasks[ tid ];

		m_neededGameSpyObjects &= ~( eGSOB_GPConnection | eGSOB_Peer );
		pTask->dataNum[ GAMESPY_ONLINE_STATE_PARAM ] = 0;

		switch ( m_states[ eGSO_GPConnection ] )
		{
		case eCGSOS_NotInitialized:
			// FALL THROUGH
		case eCGSOS_Initialized:
			pTask->dataNum[ GAMESPY_ONLINE_STATE_PARAM ] |= eGSOB_GPConnection;
			break;
		}

		switch ( m_states[ eGSO_Peer ] )
		{
		case eCGSOS_NotInitialized:
			// FALL THROUGH
		case eCGSOS_Initialized:
			pTask->dataNum[ GAMESPY_ONLINE_STATE_PARAM ] |= eGSOB_Peer;
			break;
		}

		FROM_GAME_TO_LOBBY( &CCryGameSpyLobbyService::StartTaskRunning, this, tid );
	}

	return error;
}

void CCryGameSpyLobbyService::StartUserSignOut( CryLobbyServiceTaskID lsTaskID )
{
	// Nothing to do
}

void CCryGameSpyLobbyService::TickUserSignOut( CryLobbyServiceTaskID lsTaskID )
{
	STask*									pTask = &m_tasks[ lsTaskID ];

	if ( !( pTask->dataNum[ GAMESPY_ONLINE_STATE_PARAM ] & eGSOB_GPConnection ) )
	{
		switch ( m_states[ eGSO_GPConnection ] )
		{
		case eCGSOS_Connected:
			GameSpyDisconnect( eCLE_Success );
			// FALL THROUGH
		case eCGSOS_NotInitialized:
			// FALL THROUGH
		case eCGSOS_Initialized:
			pTask->dataNum[ GAMESPY_ONLINE_STATE_PARAM ] |= eGSOB_GPConnection;
			break;
		}
	}

	if ( !( pTask->dataNum[ GAMESPY_ONLINE_STATE_PARAM ] & eGSOB_Peer ) )
	{
		switch ( m_states[ eGSO_Peer ] )
		{
		case eCGSOS_Connected:
			peerDisconnect( m_peer );
			// FALL THROUGH
		case eCGSOS_NotInitialized:
			// FALL THROUGH
		case eCGSOS_Initialized:
			pTask->dataNum[ GAMESPY_ONLINE_STATE_PARAM ] |= eGSOB_Peer;
			break;
		}
	}

	if ( pTask->dataNum[ GAMESPY_ONLINE_STATE_PARAM ] == ( eGSOB_GPConnection | eGSOB_Peer ) )
	{
		StopTaskRunning( lsTaskID );
	}
}

ECryLobbyError CCryGameSpyLobbyService::GetUserCredentials( uint32 user, CryLobbyTaskID* pTaskID, CryLobbyGetUserCredentialsCallback pCB, void* pCBArg )
{
	ECryLobbyError				error;
	CryLobbyServiceTaskID	tid;

	LOBBY_AUTO_LOCK;

	error = StartTask( eT_GetUserCredentials, user, false, &tid, pTaskID, ( void* )pCB, pCBArg );

	if ( error == eCLE_Success )
	{
		STask*									pTask = &m_tasks[ tid ];

		pTask->dataNum[ GAMESPY_USER_CREDENTIALS_PARAM ] = 0;
		FROM_GAME_TO_LOBBY( &CCryGameSpyLobbyService::StartTaskRunning, this, tid );
	}

	return error;
}

void CCryGameSpyLobbyService::StartGetUserCredentials( CryLobbyServiceTaskID lsTaskID )
{
	SetNeedGPConnection( true );
	SetNeedPeer( true );
}

void CCryGameSpyLobbyService::TickGetUserCredentials( CryLobbyServiceTaskID lsTaskID )
{
	STask*									pTask = &m_tasks[ lsTaskID ];

	if ( ( m_states[ eGSO_GPConnection ] == eCGSOS_Connected ) && ( m_states[ eGSO_Peer ] == eCGSOS_Connected ) )
	{
		if ( m_email.length() && m_password.length() && m_nick.length() && m_uniqueNick.length() )
		{
			StopTaskRunning( lsTaskID );
		}
		else
		{
			if ( !( pTask->dataNum[ GAMESPY_USER_CREDENTIALS_PARAM ] & eGUCF_StartedSearch ) )
			{
				GPResult								result;
				ECryLobbyError					error;

				result = gpProfileSearch( &m_GPConnection, m_nick.c_str(), m_uniqueNick.c_str(), m_email.c_str(), NULL, NULL, 0, GP_NON_BLOCKING, GetUserCredentialsProfileSearchCallback, this );
				error = GetErrorFromGameSpy( result, &m_GPConnection );

				if ( error == eCLE_Success )
				{
					pTask->dataNum[ GAMESPY_USER_CREDENTIALS_PARAM ] |= eGUCF_StartedSearch;
				}
				else
				{
					UpdateTaskError( lsTaskID, error );
					StopTaskRunning( lsTaskID );
				}
			}
		}
	}
}

void CCryGameSpyLobbyService::EndGetUserCredentials( CryLobbyServiceTaskID lsTaskID )
{
	STask*																				pTask = &m_tasks[ lsTaskID ];
	std::map< ECryLobbyUserCredential, string >		credentials;
	std::map< ECryLobbyUserCredential, string >*	pCredentials = NULL;

	if ( pTask->error == eCLE_Success )
	{
		credentials[ eCLUC_Email ] = string( m_email );
		credentials[ eCLUC_Password ] = string( m_password );
		credentials[ eCLUC_ProfileNick ] = string( m_nick );
		credentials[ eCLUC_UniqueNick ] = string( m_uniqueNick );
		pCredentials = &credentials;
	}

	( ( CryLobbyGetUserCredentialsCallback )pTask->pCB )( pTask->lTaskID, pTask->error, pCredentials, pTask->pCBArg );
}

void CCryGameSpyLobbyService::GetUserCredentialsProfileSearchCallback( GPConnection* pGPConnection, void* pArg, void* pParam )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	GPProfileSearchResponseArg*	pProfileSearchResponseArg = static_cast< GPProfileSearchResponseArg* >( pArg );

	if ( ( pProfileSearchResponseArg->numMatches == 1 ) && ( pProfileSearchResponseArg->more == GP_DONE ) )
	{
		if ( !pThis->m_email.length() )
		{
			pThis->m_email = pProfileSearchResponseArg->matches[ 0 ].email;
		}

		if ( !pThis->m_nick.length() )
		{
			pThis->m_nick = pProfileSearchResponseArg->matches[ 0 ].nick;
		}

		if ( !pThis->m_uniqueNick.length() )
		{
			pThis->m_uniqueNick = pProfileSearchResponseArg->matches[ 0 ].uniquenick;
		}
	}
	else
	{
		for ( CryLobbyServiceTaskID lsTaskID = 0; lsTaskID < MAX_LOBBY_TASKS; ++lsTaskID )
		{
			STask*											pTask = &pThis->m_tasks[ lsTaskID ];

			if ( pTask->used && pTask->running && ( pTask->startedTask == eT_GetUserCredentials ) )
			{
				pThis->UpdateTaskError( lsTaskID, eCLE_InternalError );
				pThis->StopTaskRunning( lsTaskID );
			}
		}

		pProfileSearchResponseArg->more = GP_DONE;
	}
}

ECryLobbyError CCryGameSpyLobbyService::CheckProfanity( const char* const pString, CryLobbyTaskID* pTaskID, CryLobbyCheckProfanityCallback pCb, void* pCbArg )
{
	LOBBY_AUTO_LOCK;

	CryLobbyServiceTaskID	lsTaskID;
	CryLobbyTaskID				lTaskID;
	ECryLobbyError				error = StartTask( eT_CheckProfanity, false, 0, &lsTaskID, &lTaskID, ( void* )pCb, pCbArg );
	uint32								strLen = strlen( pString ) + 1;

	if ( error == eCLE_Success )
	{
		error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_STRING_MEM_PARAM, NULL, strLen );

		if ( error == eCLE_Success )
		{
			error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_STRIPPED_STRING_MEM_PARAM, NULL, strLen );

			if ( error == eCLE_Success )
			{
				error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_REQUEST_MEM_PARAM, NULL, sizeof ( SAKESearchForRecordsInput ) );

				if ( error == eCLE_Success )
				{
					error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_TABLE_ID_MEM_PARAM, NULL, strlen( pGameSpyProfanityTableName ) + 1 );

					if ( error == eCLE_Success )
					{
						error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_FIELD_LIST_MEM_PARAM, NULL, sizeof ( char* ) );

						if ( error == eCLE_Success )
						{
							error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_FIELD_DATA_MEM_PARAM, NULL, strlen( pGameSpyProfanityFieldName ) + 1 );

							if ( error == eCLE_Success )
							{
								uint32		filterSize = strlen( pGameSpyProfanityFilterFormat ) + strlen( pString ) + strlen( pGameSpyProfanityFieldName ) - 3;

								error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_FILTER_MEM_PARAM, NULL, filterSize );

								if ( error == eCLE_Success )
								{
									error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_SORT_MEM_PARAM, NULL, strlen( pGameSpyProfanitySort ) + 1 );

									if ( error == eCLE_Success )
									{
										error = CreateTaskParamMem( lsTaskID, GAMESPY_CHECK_PROFANITY_REQUEST_ID_MEM_PARAM, NULL, sizeof ( SAKERequest ) );

										if ( error == eCLE_Success )
										{
											STask*											pTask = &m_tasks[ lsTaskID ];
											char*												pStrippedStringParam = static_cast< char* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_STRIPPED_STRING_MEM_PARAM ] ) );
											char*												pStringParam = static_cast< char* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_STRING_MEM_PARAM ] ) );
											SAKESearchForRecordsInput*	pRequest = static_cast< SAKESearchForRecordsInput* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_REQUEST_MEM_PARAM ] ) );
											uint32											wordIndex = 0;

											strcpy( pStringParam, pString );

											for (uint32 i = 0; i < strLen - 1; i++)
											{
												if (isalnum(pString[i]))
												{
													pStrippedStringParam[wordIndex++] = pString[i];
												}
											}

											pStrippedStringParam[wordIndex] = 0;

											pRequest->mTableId = static_cast< char* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_TABLE_ID_MEM_PARAM ] ) );
											strcpy( pRequest->mTableId, pGameSpyProfanityTableName );
											pRequest->mFieldNames = static_cast< char** >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_FIELD_LIST_MEM_PARAM ] ) );
											pRequest->mFieldNames[ 0 ] = static_cast< char* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_FIELD_DATA_MEM_PARAM ] ) );
											strcpy( pRequest->mFieldNames[ 0 ], pGameSpyProfanityFieldName );
											pRequest->mNumFields = 1;
											pRequest->mFilter = static_cast< char* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_FILTER_MEM_PARAM ] ) );
											snprintf( pRequest->mFilter, filterSize, pGameSpyProfanityFilterFormat, pStrippedStringParam, pGameSpyProfanityFieldName );
											pRequest->mSort = static_cast< char* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_SORT_MEM_PARAM ] ) );
											strcpy( pRequest->mSort, pGameSpyProfanitySort );
											pRequest->mOffset = 0;
											pRequest->mMaxRecords = 100;
											pRequest->mTargetRecordFilter = NULL;
											pRequest->mSurroundingRecordsCount = 0;
											pRequest->mOwnerIds = NULL;
											pRequest->mNumOwnerIds = 0;
											pRequest->mCacheFlag = false;

											pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] = GAMESPY_CHECK_PROFANITY_RESULT_NOT_STARTED;

											if ( pTaskID )
											{
												*pTaskID = lTaskID;
											}

											FROM_GAME_TO_LOBBY( &CCryGameSpyLobbyService::StartTaskRunning, this, lsTaskID );
										}
									}
								}
							}
						}
					}
				}
			}
		}

		if ( error != eCLE_Success )
		{
			FreeTask( lsTaskID );
		}
	}

	return error;
}

void CCryGameSpyLobbyService::StartCheckProfanity( CryLobbyServiceTaskID lsTaskID )
{
}

void CCryGameSpyLobbyService::TickCheckProfanity( CryLobbyServiceTaskID lsTaskID )
{
	STask*					pTask = &m_tasks[ lsTaskID ];

	if ( pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] == GAMESPY_CHECK_PROFANITY_RESULT_NOT_STARTED )
	{
		ICryStats*			pStats = m_pStats.get();

		if ( pStats )
		{
			SAKESearchForRecordsInput*	pRequest = static_cast< SAKESearchForRecordsInput* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_REQUEST_MEM_PARAM ] ) );
			SAKERequest*								pRequestID = static_cast< SAKERequest* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_REQUEST_ID_MEM_PARAM ] ) );
			
			*pRequestID = static_cast< CCryGameSpyStats* >( pStats )->SakeSearchForRecords( pRequest, CheckProfanitySakeSearchCallback, this );

			if ( *pRequestID )
			{
				pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] = GAMESPY_CHECK_PROFANITY_RESULT_IN_PROGRESS;
			}
			else
			{
				pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] = GAMESPY_CHECK_PROFANITY_RESULT_FAILED;
			}
		}
	}
	else
	{
		if ( pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] != GAMESPY_CHECK_PROFANITY_RESULT_IN_PROGRESS )
		{
			if ( pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] == GAMESPY_CHECK_PROFANITY_RESULT_FAILED )
			{
				UpdateTaskError( lsTaskID, eCLE_InternalError );
			}

			StopTaskRunning( lsTaskID );
		}
	}
}

void CCryGameSpyLobbyService::EndCheckProfanity( CryLobbyServiceTaskID lsTaskID )
{
	STask*											pTask = &m_tasks[ lsTaskID ];
	char*												pString = static_cast< char* >( m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_STRING_MEM_PARAM ] ) );
	bool												isProfanity = pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] == GAMESPY_CHECK_PROFANITY_RESULT_PROFANITY;

	( ( CryLobbyCheckProfanityCallback )pTask->pCB )( pTask->lTaskID, pTask->error, pString, isProfanity, pTask->pCBArg );
}

void CCryGameSpyLobbyService::CheckProfanitySakeSearchCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void* pInput, void* pOutput, void* pUserData )
{
	CCryGameSpyLobbyService*		pThis = static_cast< CCryGameSpyLobbyService* >( pUserData );

	for ( CryLobbyServiceTaskID lsTaskID = 0; lsTaskID < MAX_LOBBY_TASKS; ++lsTaskID )
	{
		STask*											pTask = &pThis->m_tasks[ lsTaskID ];

		if ( pTask->used && pTask->running && ( pTask->startedTask == eT_CheckProfanity ) )
		{
			SAKERequest*								pRequestID = static_cast< SAKERequest* >( pThis->m_pLobby->MemGetPtr( pTask->dataMem[ GAMESPY_CHECK_PROFANITY_REQUEST_ID_MEM_PARAM ] ) );

			if ( request == *pRequestID )
			{
				SAKESearchForRecordsOutput*	pSearchForRecordsOutput = static_cast< SAKESearchForRecordsOutput* >( pOutput );

				switch ( result )
				{
				case SAKERequestResult_SUCCESS:
					if ( pSearchForRecordsOutput->mNumRecords )
					{
						char* pStringParam = static_cast<char*>(pThis->m_pLobby->MemGetPtr(pTask->dataMem[GAMESPY_CHECK_PROFANITY_STRING_MEM_PARAM]));
						int len = strlen(pStringParam) + 1;

						for (uint32 i = 0; i < len; i++)
						{
							if (pStringParam[i] && !isspace(pStringParam[i]))
							{
								const int maxLength = 128;
								char word[maxLength];
								int wordIndex = 0;

								// Word starts at i
								for (uint32 j = i; j < len; j++)
								{
									if (pStringParam[j] && !isspace(pStringParam[j]))
									{
										if (isalnum(pStringParam[j]))
										{
											// Make the word lower case. Bad words come back lower case so by doing this we don't need a case insensitive strstr.
											word[wordIndex++] = tolower(pStringParam[j]);
										}
									}

									if ((pStringParam[j] == 0) || isspace(pStringParam[j]) || (wordIndex == (maxLength - 1)))
									{
										// Word ends at j
										bool bad = false;

										word[wordIndex] = 0;

										for (uint32 k = 0; k < pSearchForRecordsOutput->mNumRecords; k++)
										{
											char testword[maxLength];

											if (pSearchForRecordsOutput->mRecords[k]->mType == SAKEFieldType_ASCII_STRING)
											{
												snprintf(testword, maxLength, "%s", pSearchForRecordsOutput->mRecords[k]->mValue.mAsciiString);
											}
											else if (pSearchForRecordsOutput->mRecords[k]->mType == SAKEFieldType_UNICODE_STRING)
											{
												snprintf(testword, maxLength, "%S", pSearchForRecordsOutput->mRecords[k]->mValue.mUnicodeString);
											}

											testword[maxLength - 1] = 0;

											if (strlen(testword) < 4)
											{
												// If the profanity has 3 or less letters match exact word as the letters are likely to appear in ok words. e.g gl(ass) and gr(ass)
												if (strcmp(word, testword) == 0)
												{
													bad = true;
													break;
												}
											}
											else
											{
												// 4 or more letters are much less likely to appear in ok words so block any word that contains it
												if (strstr(word, testword))
												{
													bad = true;
													break;
												}
											}
										}

										if (bad)
										{
											for (uint32 k = i; k < j; k++)
											{
												if (isalnum(pStringParam[k]))
												{
													pStringParam[k] = '*';
												}
											}
										}

										i = j;
										break;
									}
								}
							}
						}

						// Return no profanity as we shouldn't have any anymore
						pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] = GAMESPY_CHECK_PROFANITY_RESULT_NO_PROFANITY;
					}
					else
					{
						pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] = GAMESPY_CHECK_PROFANITY_RESULT_NO_PROFANITY;
					}
					break;
				case SAKERequestResult_RECORD_NOT_FOUND:
					pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] = GAMESPY_CHECK_PROFANITY_RESULT_NO_PROFANITY;
					break;
				default:
					pTask->dataNum[ GAMESPY_CHECK_PROFANITY_RESULT_NUM_PARAM ] = GAMESPY_CHECK_PROFANITY_RESULT_FAILED;
					break;
				}

				break;
			}
		}
	}
}

void CCryGameSpyLobbyService::LocalCDKeyAuthCallback( int gameid, int localid, int authenticated, char* pErrMsg, void* pParam )
{
	CCryGameSpyLobbyService*	pThis = static_cast< CCryGameSpyLobbyService* >( pParam );

	if ( authenticated )
	{
		pThis->m_usingCDKey = true;
	}

	for ( uint32 i = 0; i < MAX_LOBBY_TASKS; ++i )
	{
		STask*					pTask = &pThis->m_tasks[ i ];

		if ( pTask->used && pTask->running && pTask->startedTask == eT_GetUserPrivileges )
		{
			if ( authenticated )
			{
				pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_STATE_PARAM ] = GAMESPY_GET_USER_PRIVILEGES_CD_KEY_SUCCEEDED;
			}
			else
			{
				// Arrr, shiver me timbers!				
				pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_STATE_PARAM ] = GAMESPY_GET_USER_PRIVILEGES_CD_KEY_FAILED;
				pTask->dataNum[ GAMESPY_GET_USER_PRIVILEGES_CD_KEY_ERROR_PARAM ] = pThis->GetGameSpyMatchMaking()->TranslateCDKeyError( pErrMsg );
			}
		}
	}
}

void CCryGameSpyLobbyService::LocalCDKeyReauthCallback( int gameid, int localid, int hint, char* pChallenge, void* pParam )
{
	CCryGameSpyLobbyService*	pThis = static_cast< CCryGameSpyLobbyService* >( pParam );
	char											response[ GAMESPY_CD_KEY_RESPONSE_SIZE ];
	SConfigurationParams			neededInfo[ 1 ] =
	{
		{ CLCC_GAMESPY_CDKEY,						{ NULL } }
	};

	pThis->m_pLobby->GetConfigurationInformation( neededInfo, ARRAY_COUNT( neededInfo ) );
	gcd_compute_response( static_cast< char* >( neededInfo[ 0 ].m_pData ), pChallenge, response, CDResponseMethod_REAUTH );
	gcd_process_reauth( pThis->m_gameID, localid, hint, response );
}

uint32 CCryGameSpyLobbyService::GetGameID()
{
	return m_gameID;
}

#endif // USE_CRYLOBBY_GAMESPY
